mcp-dataverse 0.4.7 → 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/README.md +35 -20
- package/dist/auth-provider.factory-IVYKBXVY.js +1 -0
- package/dist/chunk-KJ3HM2VM.js +1 -0
- package/dist/chunk-M5UROAVJ.js +4 -0
- package/dist/chunk-OQ46VPYS.js +3 -0
- package/dist/chunk-RYRO3QPE.js +29 -0
- package/dist/config.loader-YZJ7QTCV.js +1 -0
- package/dist/doctor.js +2 -2
- package/dist/entra-jwt-validator-DABIEBOV.js +2 -0
- package/dist/http-server.js +8 -4
- package/dist/install.js +17 -8
- package/dist/server.js +4 -4
- package/dist/setup-auth.js +1 -1
- package/package.json +3 -2
- package/server.json +2 -2
- package/dist/auth-provider.factory-MSMLSOX3.js +0 -1
- package/dist/chunk-24RDOMG4.js +0 -29
- package/dist/chunk-PAX4NW5B.js +0 -1
- package/dist/chunk-SUDI4JM6.js +0 -3
- package/dist/config.loader-VTIKUDN7.js +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**The most complete MCP server for Microsoft Dataverse.**
|
|
8
8
|
|
|
9
|
-
73 tools · 4 resources · 10 guided workflows ·
|
|
9
|
+
73 tools · 4 resources · 10 guided workflows · Three auth modes
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/mcp-dataverse)
|
|
12
12
|
[](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
|
-
- **
|
|
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
|
-
|
|
51
|
+
Three modes — choose based on where the server runs:
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
71
|
+
| Category | Count | Description |
|
|
64
72
|
| ----------------------- | ----- | -------------------------------------------------------------- |
|
|
65
|
-
| **Metadata**
|
|
66
|
-
| **Query**
|
|
67
|
-
| **CRUD**
|
|
68
|
-
| **
|
|
69
|
-
| **
|
|
70
|
-
| **
|
|
71
|
-
| **
|
|
72
|
-
| **
|
|
73
|
-
| **
|
|
74
|
-
|
|
|
75
|
-
| **
|
|
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
|
|
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 @@
|
|
|
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};
|
package/dist/doctor.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
async function f(){let
|
|
3
|
-
`),
|
|
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};
|
package/dist/http-server.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import{createServer as
|
|
2
|
-
`)
|
|
3
|
-
|
|
4
|
-
`);
|
|
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};
|
package/dist/install.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as
|
|
3
|
-
`),
|
|
4
|
-
`);function
|
|
5
|
-
`,"utf-8"),
|
|
6
|
-
e.g. https://
|
|
7
|
-
\u203A `)).trim().replace(/\/$/,"")
|
|
8
|
-
`);continue}
|
|
9
|
-
|
|
2
|
+
import{a as y}from"./chunk-RYRO3QPE.js";import{a as S}from"./chunk-KJ3HM2VM.js";import{createInterface as _}from"readline/promises";import{mkdirSync as N,writeFileSync as H}from"fs";import{homedir as $}from"os";import{join as T}from"path";import{execFileSync as w}from"child_process";import{existsSync as b,readFileSync as M,readdirSync as k,writeFileSync as V}from"fs";import{homedir as L}from"os";import{join as f}from"path";var e=n=>{process.stdout.write(n+`
|
|
3
|
+
`)},l=()=>{process.stdout.write("\u2500".repeat(58)+`
|
|
4
|
+
`)};function R(n){let r=JSON.stringify({name:"mcp-dataverse",type:"stdio",command:"npx",args:["-y","mcp-dataverse"],env:{MCP_CONFIG_PATH:n}}),a=[{label:"VS Code Insiders",bin:"code-insiders"},{label:"VS Code Stable",bin:"code"}],o=[];for(let{label:i,bin:d}of a)try{let c=process.platform==="win32"?["/c",d,"--add-mcp",r]:["--add-mcp",r],p=process.platform==="win32"?"cmd.exe":d;w(p,c,{stdio:"pipe",timeout:15e3}),o.push({label:i,ok:!0})}catch{o.push({label:i,ok:!1})}return o}function P(n){let r=JSON.stringify({name:"mcp-dataverse",type:"http",url:n}),a=[{label:"VS Code Insiders",bin:"code-insiders"},{label:"VS Code Stable",bin:"code"}],o=[];for(let{label:i,bin:d}of a)try{let c=process.platform==="win32"?["/c",d,"--add-mcp",r]:["--add-mcp",r],p=process.platform==="win32"?"cmd.exe":d;w(p,c,{stdio:"pipe",timeout:15e3}),o.push({label:i,ok:!0})}catch{o.push({label:i,ok:!1})}return o}function x(){let n=L();switch(process.platform){case"win32":{let r=process.env.APPDATA??f(n,"AppData","Roaming");return[{label:"VS Code Insiders",dir:f(r,"Code - Insiders","User")},{label:"VS Code Stable",dir:f(r,"Code","User")}]}case"darwin":return[{label:"VS Code Insiders",dir:f(n,"Library","Application Support","Code - Insiders","User")},{label:"VS Code Stable",dir:f(n,"Library","Application Support","Code","User")}];default:return[{label:"VS Code Insiders",dir:f(n,".config","Code - Insiders","User")},{label:"VS Code Stable",dir:f(n,".config","Code","User")}]}}function g(n){let r=[];for(let{dir:a}of x()){let o=f(a,"profiles");if(!b(o))continue;let i;try{i=k(o)}catch{continue}for(let d of i){let c=f(o,d,"mcp.json");if(b(c))try{let p=M(c,"utf-8"),m=JSON.parse(p);m.servers||(m.servers={}),m.servers["mcp-dataverse"]=n,V(c,JSON.stringify(m,null," ")+`
|
|
5
|
+
`,"utf-8"),r.push(c)}catch{}}}return r}function A(n){if(!n.startsWith("https://"))return"URL must start with https://";try{if(!new URL(n).hostname.toLowerCase().endsWith(".dynamics.com"))return"Must be a *.dynamics.com URL (your Power Platform environment)"}catch{return"Invalid URL format"}return null}function I(n){e(" Add this to your VS Code mcp.json (Ctrl+Shift+P \u2192 MCP: Open User Configuration):"),e(""),e(' "mcp-dataverse": {'),e(' "type": "http",'),e(` "url": "${n}"`),e(" }")}function E(n){e(" Add this to your VS Code mcp.json (Ctrl+Shift+P \u2192 MCP: Open User Configuration):"),e(""),e(' "mcp-dataverse": {'),e(' "type": "stdio",'),e(' "command": "npx",'),e(' "args": ["-y", "mcp-dataverse"],'),e(` "env": { "MCP_CONFIG_PATH": "${n.replace(/\\/g,"\\\\")}" }`),e(" }")}var U=T($(),".mcp-dataverse"),v=T(U,"config.json");async function X(){e(""),e("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),e("\u2551 MCP Dataverse \u2014 Interactive Setup \u2551"),e("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),e("");let n=_({input:process.stdin,output:process.stdout});e("Step 1 / 2 \u2014 Connection Mode"),l(),e(" How will you use the MCP Dataverse server?"),e(""),e(" 1) HTTP (hosted server) The server is already deployed on Azure."),e(" You just need its endpoint URL."),e(" \u2192 Dev connects via HTTPS, VS Code handles auth."),e(""),e(" 2) stdio (local server) The server runs on this machine via npx."),e(" You need a Dataverse URL + credentials."),e("");let r="http";for(;;){let t=(await n.question("Select [1/2] default 1: ")).trim();if(!t||t==="1"){r="http";break}if(t==="2"){r="stdio";break}e(` \u2717 Please enter 1 or 2.
|
|
6
|
+
`)}if(e(` \u2713 ${r==="http"?"HTTP (hosted)":"stdio (local)"}`),e(""),r==="http"){e("Step 2 / 2 \u2014 MCP Server Endpoint"),l(),e(" The HTTPS URL of your hosted MCP Dataverse server."),e(" e.g. https://mcp-dataverse-api.azurewebsites.net/mcp"),e(""),e(" Authentication against the server is handled automatically by VS Code"),e(" using your signed-in Microsoft account (Entra ID)."),e("");let t="";for(;!t;){let u=(await n.question(`Server URL
|
|
7
|
+
\u203A `)).trim().replace(/\/$/,"");if(!u.startsWith("https://")){e(` \u2717 URL must start with https://
|
|
8
|
+
`);continue}try{new URL(u)}catch{e(` \u2717 Invalid URL format.
|
|
9
|
+
`);continue}t=u}e(` \u2713 ${t}`),e(""),n.close(),e("Registering in VS Code\u2026");let s=P(t),h=s.filter(u=>u.ok);if(h.length>0){for(let{label:u}of h)e(` \u2713 ${u}`);for(let{label:u}of s.filter(D=>!D.ok))e(` \u2013 ${u}: not found on PATH (skipped)`)}else e(" \u26A0 Neither 'code' nor 'code-insiders' found on PATH."),e(" Register manually:"),e(""),I(t);let C=g({type:"http",url:t});C.length>0&&e(` + Patched ${C.length} custom VS Code profile(s)`),l(),e(""),e(" \u2713 Setup complete!"),e(""),e(" Restart VS Code \u2014 the MCP Dataverse tools will be available in Copilot chat."),e(" VS Code will sign you in automatically with your Microsoft account."),e("");return}e("Step 2 / 3 \u2014 Dataverse Environment"),l(),e(" The URL of your Power Platform / Dataverse environment."),e(" Find it in https://admin.powerplatform.microsoft.com \u2192 Environments \u2192 <your env>."),e("");let a="";for(;!a;){let t=(await n.question(`Environment URL (e.g. https://contoso.crm.dynamics.com)
|
|
10
|
+
\u203A `)).trim().replace(/\/$/,""),s=A(t);if(s){e(` \u2717 ${s}
|
|
11
|
+
`);continue}a=t}e(` \u2713 ${a}`),e(""),e("Step 3 / 3 \u2014 Authentication Method"),l(),e(" 1) Device Code Sign in with your Microsoft account in a browser"),e(" Recommended for individual developers"),e(""),e(" 2) Client Credentials Use an Azure App Registration (service / CI/CD)"),e(" Requires Tenant ID \xB7 Client ID \xB7 Client Secret"),e(""),e(" \u2139 Need Managed Identity? Go back and choose 'On Azure / hosted' (option 1)."),e(" Managed Identity only works when the server runs on Azure \u2014 not locally."),e("");let o="device-code";for(;;){let t=(await n.question("Select [1/2] default 1: ")).trim();if(!t||t==="1"){o="device-code";break}if(t==="2"){o="client-credentials";break}e(` \u2717 Please enter 1 or 2.
|
|
12
|
+
`)}e(` \u2713 ${o}`),e("");let i={environmentUrl:a,authMethod:o};if(o==="client-credentials"){e("Step 4 / 4 \u2014 App Registration Details"),l(),e(" Enter the values from Azure Portal \u2192 App registrations \u2192 your app."),e(" You can also supply these as environment variables (recommended for secrets)."),e("");let t="";for(;!t;)t=(await n.question(`Tenant ID (env: AZURE_TENANT_ID)
|
|
13
|
+
\u203A `)).trim(),t||e(` \u2717 Required.
|
|
14
|
+
`);let s="";for(;!s;)s=(await n.question(`Client ID (env: AZURE_CLIENT_ID)
|
|
15
|
+
\u203A `)).trim(),s||e(` \u2717 Required.
|
|
16
|
+
`);e(""),e(" Client Secret: leave blank to provide it via env var AZURE_CLIENT_SECRET"),e(" (safer \u2014 avoids storing the secret in config.json)."),e("");let h=(await n.question(`Client Secret (env: AZURE_CLIENT_SECRET) [Enter to skip]
|
|
17
|
+
\u203A `)).trim();i.tenantId=t,i.clientId=s,h?(i.clientSecret=S(h),e(""),e(" \u2713 Secret encrypted (AES-256-GCM, machine-bound key) and stored in config.json."),e(" \u26A0 Do not commit config.json to version control.")):(e(""),e(" \u2192 Set AZURE_CLIENT_SECRET in your environment or in the 'env' block of mcp.json.")),e("")}n.close(),e("Saving configuration\u2026"),N(U,{recursive:!0}),H(v,JSON.stringify(i,null,2)+`
|
|
18
|
+
`,{encoding:"utf-8",mode:384}),e(` \u2713 ${v}`),e(""),e("Registering in VS Code\u2026");let d={type:"stdio",command:"npx",args:["-y","mcp-dataverse"],env:{MCP_CONFIG_PATH:v}},c=R(v),p=c.filter(t=>t.ok);if(p.length>0){for(let{label:t}of p)e(` \u2713 ${t}`);for(let{label:t}of c.filter(s=>!s.ok))e(` \u2013 ${t}: not found on PATH (skipped)`)}else e(" \u26A0 Neither 'code' nor 'code-insiders' found on PATH."),e(" Register manually:"),e(""),E(v);let m=g(d);if(m.length>0&&e(` + Patched ${m.length} custom VS Code profile(s)`),e(""),l(),o!=="device-code"){e(""),e(" \u2713 Setup complete!"),e(""),e(" Restart VS Code to activate the MCP server."),o==="client-credentials"&&!i.clientSecret&&e(" Remember to set AZURE_CLIENT_SECRET before starting the server."),e("");return}e("Authenticating with Microsoft\u2026"),e("(Open the URL shown below and enter the code to complete sign-in)"),e("");try{await new y(a).setupViaDeviceCode(),l(),e(""),e(" \u2713 Setup complete!"),e(""),p.length>0?(e(" The MCP server is registered in VS Code."),e(" Open VS Code \u2192 Copilot chat \u2014 your Dataverse tools are ready.")):(e(" Config and authentication saved."),e(" Register the server manually (see snippet above), then restart VS Code.")),e("")}catch(t){let s=t instanceof Error?t.message:String(t);l(),e(` \u2717 Authentication failed: ${s}`),e(""),e(" Configuration was saved. Run 'npx mcp-dataverse install' again to retry."),e(""),process.exit(1)}}export{X as runInstall};
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as he}from"./chunk-SUDI4JM6.js";import{a as be}from"./chunk-PAX4NW5B.js";import"./chunk-24RDOMG4.js";import{a as v,b as _e}from"./chunk-FSM3J3WD.js";import{Server as Wn}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as jn}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fn,ListToolsRequestSchema as Bn,ListResourcesRequestSchema as Gn,ListResourceTemplatesRequestSchema as zn,ReadResourceRequestSchema as Vn}from"@modelcontextprotocol/sdk/types.js";import{readFileSync as Kn}from"fs";import{join as Xn,dirname as Qn}from"path";import{fileURLToPath as Yn}from"url";function we(){let a=process.argv.slice(2),i="stdio",r=3e3;for(let e=0;e<a.length;e++){if(a[e]==="--transport"&&a[e+1]){let t=a[e+1];(t==="http"||t==="stdio")&&(i=t),e++}if(a[e]==="--port"&&a[e+1]){let t=parseInt(a[e+1],10);!isNaN(t)&&t>0&&t<65536&&(r=t),e++}}return{transport:i,port:r}}var se=class{map=new Map;register(i){for(let r of i.tools){if(this.map.has(r.name))throw new Error(`Duplicate tool name: ${r.name}`);this.map.set(r.name,{definition:r,handler:i.handler})}}getHandler(i){return this.map.get(i)?.handler}getAllDefinitions(){return Array.from(this.map.values()).map(i=>i.definition)}has(i){return this.map.has(i)}get size(){return this.map.size}};function Se(a){let i=new se;for(let r of a)i.register(r);return i}var J=class{server;token;constructor(i,r){this.server=i??null,this.token=r??null}async report(i,r){if(!(!this.server||this.token==null))try{await this.server.notification({method:"notifications/progress",params:{progressToken:this.token,progress:i,total:r}})}catch{}}},rr=new J;function O(a){let i=a.errorCategory!==void 0,r=i?{...a,isError:!0,content:[{type:"text",text:a.summary}]}:a,e={content:[{type:"text",text:JSON.stringify(r,null,2)}]};return i&&(e.isError=!0),e}function u(a,i,r,e){return O({summary:a,data:i,suggestions:r,warnings:e})}function H(a,i,r){return O({summary:`${i.length} ${a} found.`,data:i,suggestions:r})}function te(a,i="UNKNOWN",r){return O({summary:a,data:null,errorCategory:i,suggestions:r})}function X(a){let i={feature_disabled:"ENV_LIMITATION",permission_required:"PERMISSIONS",schema_missing:"SCHEMA_MISMATCH"};return O({summary:`Blocked: ${a.cannotProceedBecause}`,data:null,prerequisite:a,errorCategory:i[a.type]})}var le="@OData.Community.Display.V1.FormattedValue";function G(a){return a.map(i=>{if(typeof i!="object"||i===null)return i;let r=i,e={};for(let n of Object.keys(r))if(n.endsWith(le)){let o=n.slice(0,-le.length);e[o]=r[n]}if(Object.keys(e).length===0)return i;let t={};for(let n of Object.keys(r))n.endsWith(le)||(Object.prototype.hasOwnProperty.call(e,n)?t[n]={value:r[n],label:e[n]}:t[n]=r[n]);return t})}var Te=[{name:"dataverse_whoami",description:"Returns the current authenticated user context from Dataverse WhoAmI: userId, businessUnitId, organizationId, organizationName, and environmentUrl. Use this to verify authentication is working, retrieve the current user context, or obtain IDs needed for subsequent operations. WHEN TO USE: Verifying authentication, getting current user/org context, or obtaining IDs for downstream operations. BEST PRACTICES: Call first to confirm connectivity before other tools. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}];async function Ne(a,i,r){if(a==="dataverse_whoami"){let e=await r.whoAmI(),t={userId:e.UserId,businessUnitId:e.BusinessUnitId,organizationId:e.OrganizationId,organizationName:e.OrganizationName,environmentUrl:e.EnvironmentUrl};return u(`Authenticated as ${e.OrganizationName}`,t,["Use dataverse_list_tables to discover available tables","Use dataverse_get_table_metadata to inspect a table schema"])}throw new Error(`Unknown auth tool: ${a}`)}import{z as ke}from"zod";import{z as R}from"zod";var de=[{name:"dataverse_list_tables",description:"Lists all Dataverse tables. By default returns ONLY custom (non-system) tables. Set includeSystemTables=true to include all ~1700+ system tables. WHEN TO USE: Discovering which tables exist in the environment. BEST PRACTICES: Start without includeSystemTables to focus on custom tables; use dataverse_get_table_metadata for column details. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{includeSystemTables:{type:"boolean",description:"Include system tables. Default false = custom tables only."}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_table_metadata",description:"Returns full schema metadata for a Dataverse table: all attribute (column) logical names, display names, data types, required levels, and lookup target entities. Use this before writing queries or creating/updating records to confirm correct field names and types. Set includeAttributes=false if you only need table-level metadata without column details. WHEN TO USE: Before building queries or creating/updating records to validate field names, types, and required levels. BEST PRACTICES: Set includeAttributes=false for table-level info only; call once and reuse results. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{logicalName:{type:"string",description:'The logical name of the table (e.g., "account", "contact", "new_mytable")'},includeAttributes:{type:"boolean",description:"Include attribute (column) definitions. Default: true"}},required:["logicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_relationships",description:"Returns all relationship definitions (1:N, N:1, N:N) for a Dataverse table, including relationship schema names, referenced/referencing entity names, and lookup attribute names. Use to determine the correct relationshipName for dataverse_associate/dataverse_disassociate, or to map lookup fields before building FetchXML joins. Use relationshipType to filter results. WHEN TO USE: Finding relationship schema names for associate/disassociate, or mapping lookups for FetchXML joins. BEST PRACTICES: Filter by relationshipType to reduce output size. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{logicalName:{type:"string",description:"The logical name of the table"},relationshipType:{type:"string",enum:["OneToMany","ManyToOne","ManyToMany","All"],description:"Filter by relationship type. Default: All"}},required:["logicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_global_option_sets",description:"Lists all global (shared) option sets defined in the Dataverse environment, returning their names and metadata IDs. Use this to discover available option sets before calling dataverse_get_option_set to retrieve their values. Prefer this over dataverse_get_table_metadata when you need to find option sets that are reused across multiple tables. WHEN TO USE: Discovering shared option sets reused across multiple tables. BEST PRACTICES: Follow up with dataverse_get_option_set to retrieve individual option values. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_option_set",description:"Returns all option labels and their integer values for a named global option set. Use this to look up the numeric code for a picklist value before filtering records (e.g., statecode or statuscode equivalents), or to populate dropdowns with correct option values. WHEN TO USE: Looking up numeric codes for picklist filtering or record creation. BEST PRACTICES: Use the integer values in $filter expressions, not the label text. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The name of the global option set"}},required:["name"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_entity_key",description:"Returns all alternate key definitions for a Dataverse table. Useful before using dataverse_upsert to know which fields serve as alternate keys, their index status (Active/InProgress/Failed), and whether they are customizable. WHEN TO USE: Before using dataverse_upsert to verify which fields serve as alternate keys. BEST PRACTICES: Check that key index status is Active before relying on a key. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{tableName:{type:"string",description:'Logical name of the table (e.g., "account", "contact")'}},required:["tableName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_attribute_option_set",description:"Returns all option labels and integer values for a table-specific attribute (Picklist, MultiSelectPicklist, Status, or State field). Use to look up the numeric codes for a column's choices before filtering or updating records. WHEN TO USE: Looking up option values for a specific table's picklist, multi-select picklist, status, or state column. BEST PRACTICES: Use the integer values in $filter and when setting fields in create/update. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:'Logical name of the table (e.g., "account", "contact")'},attributeLogicalName:{type:"string",description:'Logical name of the attribute (e.g., "statuscode", "industrycode", "new_tags")'}},required:["entityLogicalName","attributeLogicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Yt=R.object({logicalName:R.string().min(1),includeAttributes:R.boolean().optional().default(!0)}),Jt=R.object({logicalName:R.string().min(1),relationshipType:R.enum(["OneToMany","ManyToOne","ManyToMany","All"]).optional()}),Zt=R.object({includeSystemTables:R.boolean().default(!1).optional().describe("Include system tables. Default false = custom tables only.")}),ea=R.object({name:R.string().min(1)}),ta=R.object({tableName:R.string().min(1).describe('Logical name of the table (e.g., "account", "contact")')}),Re=/^[a-z_][a-z0-9_]*$/,aa=R.object({entityLogicalName:R.string().min(1).regex(Re,"Invalid Dataverse logical name"),attributeLogicalName:R.string().min(1).regex(Re,"Invalid Dataverse logical name")});async function Ae(a,i,r){switch(a){case"dataverse_list_tables":{let{includeSystemTables:e=!1}=Zt.parse(i??{}),t=!e,n=await r.listTables(t),o=Array.isArray(n)?n:[];return H(`tables (${t?"custom only":"including system"})`,o,["Use dataverse_get_table_metadata to inspect a specific table's columns and types"])}case"dataverse_get_table_metadata":{let{logicalName:e,includeAttributes:t}=Yt.parse(i),n=await r.getTableMetadata(e,t),o=Array.isArray(n?.Attributes)?n.Attributes.length:0;return u(`Metadata for ${e}: ${o} attributes`,n,["Use dataverse_query to read records from this table","Use dataverse_get_relationships to see related tables"])}case"dataverse_get_relationships":{let{logicalName:e,relationshipType:t}=Jt.parse(i),n=await r.getRelationships(e),o=e.toLowerCase(),l=n.filter(m=>m.RelationshipType==="OneToManyRelationship"&&m.ReferencedEntity?.toLowerCase()===o),s=n.filter(m=>m.RelationshipType==="OneToManyRelationship"&&m.ReferencingEntity?.toLowerCase()===o),d=n.filter(m=>m.RelationshipType==="ManyToManyRelationship"),c=!t||t==="All",p=[...c||t==="OneToMany"?l:[],...c||t==="ManyToOne"?s:[],...c||t==="ManyToMany"?d:[]];return u(`${p.length} relationships found for ${e}`,{tableName:e,oneToMany:c||t==="OneToMany"?l:void 0,manyToOne:c||t==="ManyToOne"?s:void 0,manyToMany:c||t==="ManyToMany"?d:void 0},["Use dataverse_associate or dataverse_disassociate to manage N:N relationships"])}case"dataverse_list_global_option_sets":{let e=await r.listGlobalOptionSets(),t=Array.isArray(e)?e:[];return H("global option sets",t,["Use dataverse_get_option_set with a name to see the values"])}case"dataverse_get_option_set":{let{name:e}=ea.parse(i),t=await r.getOptionSet(e),n=Array.isArray(t?.Options)?t.Options:[];return u(`Option set '${e}': ${n.length} options`,t,["Use these values in $filter expressions or when creating/updating records"])}case"dataverse_get_entity_key":{let{tableName:e}=ta.parse(i),t=await r.getEntityKeys(e);return u(`${t.length} alternate keys for ${e}`,{tableName:e,keys:t,count:t.length},["Use dataverse_upsert with these keys for create-or-update operations"])}case"dataverse_get_attribute_option_set":{let{entityLogicalName:e,attributeLogicalName:t}=aa.parse(i),n=await r.getAttributeOptionSet(e,t),o=Array.isArray(n?.options)?n.options:[];return u(`Attribute '${t}' on '${e}': ${o.length} options`,n,["Use these integer values in $filter or when creating/updating records"])}default:throw new Error(`Unknown metadata-read tool: ${a}`)}}import{z}from"zod";var na=new Set(["dataverse_delete","dataverse_delete_file","dataverse_update_entity","dataverse_delete_attribute"]);function ce(a){let i=[];return a.top!==void 0&&a.top>5e3&&i.push({code:"LARGE_RESULT_SET",message:`Large result set requested (top=${a.top}). Consider reducing $top or using paging.`,severity:"warning"}),(!a.select||a.select.length===0)&&i.push({code:"NO_SELECT",message:"No $select specified \u2014 all columns will be returned. Specify columns to reduce payload size.",severity:"warning"}),a.filter||i.push({code:"NO_FILTER",message:"No filter \u2014 consider adding one to reduce results and improve performance.",severity:"info"}),i}function q(a){let i=[];return na.has(a.toolName)&&i.push({code:"DESTRUCTIVE_OP",message:"Destructive operation \u2014 confirm with user before proceeding. This action cannot be undone.",severity:"warning"}),a.toolName==="dataverse_batch_execute"&&i.push({code:"BATCH_OP",message:"Batch operations may modify multiple records in a single transaction.",severity:"info"}),i}var Ee=[{name:"dataverse_update_entity",description:"Updates configuration flags on an existing Dataverse entity definition \u2014 enables or disables Notes (HasNotes), Change Tracking, and Audit. Requires System Customizer or System Administrator privileges. WHEN TO USE: Enabling notes/attachments support, change tracking, or audit on a table. BEST PRACTICES: Leave autoPublish=false (default) when making multiple changes and call dataverse_publish_customizations once at the end. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:"Logical name of the entity to update (e.g. 'account', 'new_mytable')"},hasNotes:{type:"boolean",description:"Enable or disable the Notes/Attachments feature for this entity"},changeTrackingEnabled:{type:"boolean",description:"Enable or disable change tracking (required for delta sync)"},isAuditEnabled:{type:"boolean",description:"Enable or disable auditing on this entity"},autoPublish:{type:"boolean",description:"Publish the customization after update (default: false). For batch operations leave false and publish once at the end."},confirm:{type:"boolean",description:"Must be true \u2014 confirms intentional schema modification to Dataverse entity metadata"}},required:["entityLogicalName","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ra=z.object({entityLogicalName:z.string().min(1).regex(/^[a-z_][a-z0-9_]*$/,"Invalid logical name"),confirm:z.literal(!0,{errorMap:()=>({message:"Set confirm: true to modify entity metadata"})}),hasNotes:z.boolean().optional(),changeTrackingEnabled:z.boolean().optional(),isAuditEnabled:z.boolean().optional(),autoPublish:z.boolean().optional().default(!1)}).refine(a=>a.hasNotes!==void 0||a.changeTrackingEnabled!==void 0||a.isAuditEnabled!==void 0,{message:"At least one of hasNotes, changeTrackingEnabled, or isAuditEnabled must be provided"});async function xe(a,i,r){switch(a){case"dataverse_update_entity":{let{entityLogicalName:e,hasNotes:t,changeTrackingEnabled:n,isAuditEnabled:o,autoPublish:l}=ra.parse(i),s=q({toolName:"dataverse_update_entity",entitySetName:e}).map(f=>`[${f.severity.toUpperCase()}] ${f.code}: ${f.message}`),d={};t!==void 0&&(d.HasNotes=t),n!==void 0&&(d.ChangeTrackingEnabled=n),o!==void 0&&(d.IsAuditEnabled=o);try{await r.getTableMetadata(e,!1)}catch{return O({summary:`Entity '${e}' does not exist in this Dataverse environment.`,data:{error:"ENTITY_NOT_FOUND",entityLogicalName:e},errorCategory:"SCHEMA_MISMATCH",suggestions:["Use dataverse_list_tables to find the correct entity logical name"]})}let c={"@odata.type":"#Microsoft.Dynamics.CRM.EntityMetadata"};t!==void 0&&(c.HasNotes=t),n!==void 0&&(c.ChangeTrackingEnabled=n),o!==void 0&&(c.IsAuditEnabled={Value:o});try{await r.updateEntityDefinition(e,c)}catch(f){if((f instanceof Error?f.message:String(f)).includes("0x80060888")){let K=Object.keys(d),F={IsAuditEnabled:"IsAuditEnabled requires organization-level auditing to be enabled first: Power Platform admin center > Settings > Audit and logs > Start Auditing",ChangeTrackingEnabled:"ChangeTrackingEnabled may be blocked if the entity is part of a managed solution",HasNotes:"HasNotes=true/false should work for custom (unmanaged) entities"},Y=K.map(B=>F[B]).filter(B=>B!==void 0);return O({summary:`Cannot update entity '${e}': operation not supported by Dataverse.`,data:{error:"0x80060888",entityLogicalName:e,requestedChanges:d},errorCategory:"SCHEMA_MISMATCH",suggestions:Y})}throw f}let p=Object.entries(d).map(([f,N])=>`${f}=${String(N)}`).join(", "),m="autoPublish=false (skipped)";return l&&(await r.publishCustomizations({entities:[e]}),m="published successfully"),u(`Entity '${e}' updated: ${p}. ${m}.`,{entityLogicalName:e,changes:d,published:l,...s.length>0&&{warnings:s}},["Use dataverse_get_table_metadata to verify the changes","Enable HasNotes=true before using dataverse_create_annotation"])}default:throw new Error(`Unknown metadata tool: ${a}`)}}var ia={name:"dataverse_resolve_entity_name",description:"Resolves entity names bidirectionally: input a logicalName (e.g. 'account') or entitySetName (e.g. 'accounts') and get both representations plus metadata. Essential for avoiding 404/0x80060888 errors caused by using logicalName in OData URLs (which require entitySetName). WHEN TO USE: Any time you're unsure whether to use logicalName vs entitySetName, or to look up PrimaryIdAttribute/PrimaryNameAttribute for new entities. EXAMPLES: 'account' \u2192 LogicalName:account, EntitySetName:accounts, PrimaryId:accountid. WORKFLOW: read_record, inspect_audit.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Entity name to resolve \u2014 either logicalName (e.g. 'account') or entitySetName (e.g. 'accounts')"}},required:["name"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},oa=ke.object({name:ke.string().min(1)}),Ie=[...de,ia,...Ee];async function De(a,i,r){if(a==="dataverse_resolve_entity_name"){let{name:t}=oa.parse(i),n=`LogicalName eq '${t}' or EntitySetName eq '${t}'`,l=(await r.query("EntityDefinitions",{filter:n,select:["LogicalName","EntitySetName","DisplayName","PrimaryIdAttribute","PrimaryNameAttribute","OwnershipType","IsCustomEntity"]})).value??[];if(l.length===0)return u(`No entity found matching '${t}'`,{name:t,matches:[]},["Try dataverse_list_tables to see available table names"]);let s=l[0],d=s.DisplayName?.UserLocalizedLabel?.Label??null;return u(`Resolved '${t}' \u2192 LogicalName: ${s.LogicalName}, EntitySetName: ${s.EntitySetName}`,{logicalName:s.LogicalName,entitySetName:s.EntitySetName,displayName:d,primaryIdAttribute:s.PrimaryIdAttribute,primaryNameAttribute:s.PrimaryNameAttribute,ownershipType:s.OwnershipType,isCustomEntity:s.IsCustomEntity},["Use entitySetName in OData Web API URLs (dataverse_query, dataverse_read_record)","Use logicalName in FetchXML and metadata API calls"])}return new Set(de.map(t=>t.name)).has(a)?Ae(a,i,r):xe(a,i,r)}import{z as _}from"zod";import{z as Oe}from"zod";var Ce=/^[a-zA-Z_][a-zA-Z0-9_]*$/,h=Oe.string().min(1).regex(Ce,"entitySetName must contain only letters, digits, or underscores"),ae=Oe.string().min(1).regex(Ce,"relationshipName must contain only letters, digits, or underscores");var sa={opportunity:"opportunities",territory:"territories",category:"categories",activityparty:"activityparties",activitymimeattachment:"activitymimeattachments",queue:"queues",queueitem:"queueitems",dependency:"dependencies",salesliteratureitem:"salesliteratureitems",contractdetail:"contractdetails",discounttype:"discounttypes",entitlementtemplate:"entitlementtemplates",pricelevel:"pricelevels"},Ue=[{name:"dataverse_query",description:"Queries a Dataverse table using OData ($filter, $select, $orderby, $top, $expand, $count). Use for simple to moderate reads on a single table or with shallow $expand for related fields. Always specify $select to minimize payload. For complex aggregations (count, sum, avg), multi-entity joins, many-to-many traversal, or advanced FetchXML-only operators, use dataverse_execute_fetchxml instead. WHEN TO USE: Single-table reads, simple filters, shallow expands, or server-side aggregation via $apply. BEST PRACTICES: Always pass $select; cap with $top; use $apply for server-side counts/grouping. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'The OData entity set name (e.g., "accounts", "contacts", "new_mytables")'},select:{type:"array",items:{type:"string"},description:"Columns to return. Always specify to minimize payload."},filter:{type:"string",description:'OData $filter expression (e.g., "statecode eq 0 and new_amount gt 1000")'},orderby:{type:"string",description:'OData $orderby expression (e.g., "createdon desc")'},top:{type:"number",description:"Maximum number of records to return (default: 50)"},expand:{type:"string",description:'OData $expand for related entities (e.g., "parentaccountid($select=name)")'},count:{type:"boolean",description:"Include total record count in response"},apply:{type:"string",description:'OData $apply for server-side aggregation (e.g., "groupby((statuscode),aggregate($count as count))")'},formattedValues:{type:"boolean",description:"When true, includes human-readable labels for picklist fields alongside raw integer codes (e.g., { value: 1, label: 'Active' }). Uses OData formatted-value annotations."}},required:["entitySetName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_fetchxml",description:'Executes a FetchXML query against Dataverse \u2014 use for complex scenarios requiring aggregations (count, sum, avg, min, max with grouping), linked-entity joins across multiple tables, many-to-many relationship traversal, or advanced filtering not expressible in OData. Returns a typed array of records. entitySetName is optional \u2014 if omitted it is extracted from the <entity name="..."> element in the FetchXML. Set fetchAll=true to automatically paginate through all pages (up to 100 pages / 50,000 records for safety). Default behavior returns first page only. WHEN TO USE: Multi-table joins, aggregations with groupby, N:N traversal, or filtering not supported by OData. BEST PRACTICES: Add page/count attributes for large result sets; prefer dataverse_query for simple reads; use fetchAll=true to retrieve all records beyond the first page. WORKFLOW: query_data.',inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name of the root entity (e.g., "accounts"). If omitted, extracted from the <entity name="..."> element in the FetchXML.'},fetchXml:{type:"string",description:"The complete FetchXML query string"},fetchAll:{type:"boolean",description:"When true, automatically paginates through all result pages (up to 100 pages / 50,000 records). Default false returns the first page only."},formattedValues:{type:"boolean",description:"When true, includes human-readable labels for picklist fields alongside raw integer codes."}},required:["fetchXml"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_retrieve_multiple_with_paging",description:"Retrieves ALL records matching a query by automatically following OData nextLink pages. Use instead of dataverse_query when you need more than 5000 records or all records in a table. Returns totalRetrieved count. Set maxTotal to cap retrieval (default 5000, max 50000) to avoid overwhelming the context. WHEN TO USE: You need all matching records beyond the 5000-row OData page limit or a full table export. BEST PRACTICES: Always set $select; use maxTotal to cap results and avoid context overflow. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name (e.g., "accounts")'},select:{type:"array",items:{type:"string"}},filter:{type:"string"},orderby:{type:"string"},expand:{type:"string"},maxTotal:{type:"number",description:"Maximum records to retrieve (default: 5000, max: 50000)"},formattedValues:{type:"boolean",description:"Include formatted label annotations."}},required:["entitySetName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],la=_.object({entitySetName:h,select:_.array(_.string()).optional(),filter:_.string().optional(),orderby:_.string().optional(),top:_.number().positive().max(5e3).optional().default(50),expand:_.string().optional(),count:_.boolean().optional(),apply:_.string().optional(),formattedValues:_.boolean().optional()}),da=_.object({fetchXml:_.string().min(1).describe("Complete FetchXML query string"),entitySetName:_.string().optional().describe('OData entity set name (e.g., "accounts"). If omitted, extracted from the <entity name="..."> element in the FetchXML.'),fetchAll:_.boolean().optional(),formattedValues:_.boolean().optional()}),ca=_.object({entitySetName:h,select:_.array(_.string()).optional(),filter:_.string().optional(),orderby:_.string().optional(),expand:_.string().optional(),maxTotal:_.number().positive().max(5e4).optional(),formattedValues:_.boolean().optional()});async function Le(a,i,r,e){switch(a){case"dataverse_query":{let t=la.parse(i),n={};t.select!==void 0&&(n.select=t.select),t.filter!==void 0&&(n.filter=t.filter),t.orderby!==void 0&&(n.orderby=t.orderby),t.top!==void 0&&(n.top=t.top),t.expand!==void 0&&(n.expand=t.expand),t.count!==void 0&&(n.count=t.count),t.apply!==void 0&&(n.apply=t.apply),t.formattedValues!==void 0&&(n.formattedValues=t.formattedValues);let o;try{o=await r.query(t.entitySetName,n)}catch(f){let N=f,F=N?.data?.error?.message,Y=f instanceof Error?f.message:String(f),B=N?.status,Qt=B===403||B===401?"PERMISSIONS":B===404?"SCHEMA_MISMATCH":"UNKNOWN";return te(`dataverse_query failed on '${t.entitySetName}': ${F??Y}`,Qt,["Use dataverse_list_tables to verify the entity set name","Check OData filter syntax and column names"])}let l=Array.isArray(o?.value)?o.value:[],s=o["@odata.count"],d=s!==void 0?s===l.length?" (showing all results)":` (total in dataset: ${s})`:"",c=t.formattedValues?G(l):l,p=t.formattedValues&&Array.isArray(o?.value)?{...o,value:c}:o,m=ce({...t.top!==void 0?{top:t.top}:{},...t.select!==void 0?{select:t.select}:{},...t.filter!==void 0?{filter:t.filter}:{},entitySetName:t.entitySetName}).map(f=>`[${f.severity.toUpperCase()}] ${f.code}: ${f.message}`);return u(`${l.length} records returned from ${t.entitySetName}${d}`,p,["Use dataverse_execute_fetchxml for complex joins or aggregations","Add $select to minimize payload"],m.length>0?m:void 0)}case"dataverse_execute_fetchxml":{let t=da.parse(i),n=t.entitySetName,{fetchXml:o}=t;if(!n){let c=o.match(/<entity\s+name=["']([^"']+)["']/i);if(!c)return{content:[{type:"text",text:JSON.stringify({isError:!0,error:"entitySetName is required when not present in FetchXML <entity> element"})}]};let p=c[1];n=sa[p]??p+"s"}if(t.fetchAll){let c=await r.executeFetchXmlAllPages(n,o),p=t.formattedValues?G(c):c;return u(`${p.length} records retrieved via FetchXML (all pages)`,p,["Use dataverse_query for simple OData reads","fetchAll paginated through all available pages (up to 100)"])}let l=await r.executeFetchXml(n,o,t.formattedValues),s=Array.isArray(l)?l:Array.isArray(l?.value)?l.value:[],d=t.formattedValues?G(s):s;return u(`${d.length} records returned via FetchXML`,t.formattedValues&&Array.isArray(l?.value)?{...l,value:d}:l,["Use dataverse_query for simple OData reads","Add page/count attributes for large result sets"])}case"dataverse_retrieve_multiple_with_paging":{let t=ca.parse(i),n={};t.select!==void 0&&(n.select=t.select),t.filter!==void 0&&(n.filter=t.filter),t.orderby!==void 0&&(n.orderby=t.orderby),t.expand!==void 0&&(n.expand=t.expand),t.maxTotal!==void 0&&(n.maxTotal=t.maxTotal),await e?.report(0,1);let o=await r.queryWithPaging(t.entitySetName,n),l=o?.totalRetrieved??(Array.isArray(o?.value)?o.value.length:0),s=o?.pageCount??1;await e?.report(1,1);let d=Array.isArray(o?.records)?o.records:[],c=t.formattedValues&&d.length>0?{...o,records:G(d)}:o,p=ce({...t.select!==void 0?{select:t.select}:{},...t.filter!==void 0?{filter:t.filter}:{},entitySetName:t.entitySetName}).map(m=>`[${m.severity.toUpperCase()}] ${m.code}: ${m.message}`);return u(`${l} records retrieved across ${s} pages from ${t.entitySetName}`,c,["Set maxTotal to limit retrieval","Use $select to minimize payload size"],p.length>0?p:void 0)}default:throw new Error(`Unknown query tool: ${a}`)}}import{z as y}from"zod";var Pe=[{name:"dataverse_get",description:"Retrieves a single Dataverse record by its GUID. Use when you already know the exact record ID and want specific fields \u2014 faster and more precise than dataverse_query with a GUID filter. Specify select to limit returned columns and reduce payload size. WHEN TO USE: You have the exact record GUID and want specific fields. BEST PRACTICES: Always specify select; use the returned etag for subsequent optimistic-concurrency updates. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name"},id:{type:"string",description:"Record GUID"},select:{type:"array",items:{type:"string"},description:"Columns to return"},expand:{type:"string",description:'OData $expand for related entities (e.g., "parentaccountid($select=name)")'},formattedValues:{type:"boolean",description:"When true, annotates picklist fields with human-readable labels. Note: dataverse_get always requests all OData annotations."}},required:["entitySetName","id"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_create",description:`Creates a new record in a Dataverse table and returns the new record's GUID. Use dataverse_get_table_metadata first to confirm correct logical field names and required fields. For setting lookup fields, use the format "_fieldname_value" with the related record GUID. For bulk creation of multiple records, consider dataverse_batch_execute to reduce HTTP round-trips. WHEN TO USE: Creating a single new record in a known table. BEST PRACTICES: Validate field names via dataverse_get_table_metadata first; use dataverse_batch_execute for bulk inserts. WORKFLOW: create_record.`,inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name"},data:{type:"object",description:"Record data as key-value pairs using logical names"}},required:["entitySetName","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_update",description:"Updates an existing Dataverse record using PATCH semantics \u2014 only the fields provided in data are changed, all other fields remain unchanged. Requires the record GUID. Use dataverse_upsert instead if you want to create-or-update using an alternate key without knowing the GUID upfront. WHEN TO USE: Modifying specific fields on an existing record with a known GUID. BEST PRACTICES: Pass etag for optimistic concurrency; include only changed fields in data. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},data:{type:"object",description:"Fields to update"},etag:{type:"string",description:"ETag value from a prior dataverse_get response. When provided, the update only succeeds if the record has not been modified since (optimistic concurrency). Prevents lost updates."}},required:["entitySetName","id","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_delete",description:"Permanently deletes a Dataverse record by its GUID. This operation is irreversible \u2014 you MUST set confirm=true to proceed. Use dataverse_list_dependencies to check if the record is referenced by workflows, plugins, or other components before deleting shared or configuration records. WHEN TO USE: Permanently removing a record you are certain should be deleted. BEST PRACTICES: Call dataverse_list_dependencies first for shared records; always set confirm=true. WORKFLOW: delete_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},confirm:{type:"boolean",description:"Must be explicitly true to proceed with deletion"}},required:["entitySetName","id","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_upsert",description:'Creates or updates a Dataverse record using an alternate key (no GUID needed). Returns operation="created" or "updated". Use mode="createOnly" to fail with an error if the record already exists, or mode="updateOnly" to fail if the record does not exist. Default mode="upsert" creates or updates. Supports compound alternate keys via the alternateKeys parameter. WHEN TO USE: Create-or-update when you have an alternate key but no GUID. BEST PRACTICES: Verify alternate keys with dataverse_get_entity_key first; use mode to control behavior. WORKFLOW: create_record.',inputSchema:{type:"object",properties:{entitySetName:{type:"string"},alternateKey:{type:"string",description:"Alternate key attribute name (for single key)"},alternateKeyValue:{type:"string",description:"Alternate key value (for single key)"},alternateKeys:{type:"object",description:'Compound alternate key as key-value map (e.g., {"key1":"val1","key2":"val2"}). Use instead of alternateKey/alternateKeyValue for multi-field keys.'},data:{type:"object",description:"Record data"},mode:{type:"string",enum:["upsert","createOnly","updateOnly"],description:"upsert=create or update (default), createOnly=fail if exists, updateOnly=fail if not found"}},required:["entitySetName","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_assign",description:"Assigns a Dataverse record to a different user or team owner. Sets the ownerid lookup field using the OData bind syntax. WHEN TO USE: Changing the owner of a record to a different user or team. BEST PRACTICES: Use dataverse_list_users or dataverse_list_teams to find valid owner GUIDs first. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name of the record to assign"},id:{type:"string",description:"Record GUID"},ownerType:{type:"string",enum:["systemuser","team"],description:'Type of the new owner: "systemuser" for a user, "team" for a team'},ownerId:{type:"string",description:"GUID of the user or team to assign the record to"}},required:["entitySetName","id","ownerType","ownerId"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ua=y.object({entitySetName:h,id:y.string().uuid(),select:y.array(y.string()).optional(),expand:y.string().optional(),formattedValues:y.boolean().optional()}),pa=y.object({entitySetName:h,data:y.record(y.unknown())}),ma=y.object({entitySetName:h,id:y.string().uuid(),data:y.record(y.unknown()),etag:y.string().optional()}),ga=y.object({entitySetName:h,id:y.string().uuid(),confirm:y.boolean()}),fa=y.object({entitySetName:h,alternateKey:y.string().min(1).optional(),alternateKeyValue:y.string().min(1).optional(),alternateKeys:y.record(y.string()).optional(),data:y.record(y.unknown()),mode:y.enum(["upsert","createOnly","updateOnly"]).default("upsert").optional().describe("upsert=create or update (default), createOnly=fail if exists, updateOnly=fail if not found")}).refine(a=>a.alternateKey&&a.alternateKeyValue||a.alternateKeys&&Object.keys(a.alternateKeys).length>0,{message:"Provide either (alternateKey + alternateKeyValue) or alternateKeys"}),ya=y.object({entitySetName:h,id:y.string().uuid(),ownerType:y.enum(["systemuser","team"]),ownerId:y.string().uuid()});async function qe(a,i,r){switch(a){case"dataverse_get":{let{entitySetName:e,id:t,select:n,expand:o,formattedValues:l}=ua.parse(i),s,d;try{let p=await r.getRecord(e,t,n,o);s=p.record,d=p.etag}catch(p){let m=p,N=m?.data?.error?.message,K=p instanceof Error?p.message:String(p),F=m?.status,Y=F===403||F===401?"PERMISSIONS":F===404?"SCHEMA_MISMATCH":"UNKNOWN";return te(`dataverse_get failed for '${t}' in '${e}': ${N??K}`,Y,["Use dataverse_list_tables to verify the entity set name","Verify the record ID exists with dataverse_query"])}let c=l?G([s])[0]??s:s;return u(`Retrieved record ${t} from ${e}`,{id:t,record:c,etag:d},["Use dataverse_update to modify this record","Use dataverse_get_relationships to explore related records"])}case"dataverse_create":{let{entitySetName:e,data:t}=pa.parse(i),n=await r.createRecord(e,t);return u(`Created record ${n} in ${e}`,{id:n,message:"Record created successfully"},["Use dataverse_get to retrieve the full record","Use dataverse_associate to link related records"])}case"dataverse_update":{let{entitySetName:e,id:t,data:n,etag:o}=ma.parse(i);return await r.updateRecord(e,t,n,o),u(`Updated record ${t} in ${e}`,{message:"Record updated successfully"},["Use dataverse_get to verify the update","Use etag parameter for optimistic concurrency"])}case"dataverse_delete":{let{entitySetName:e,id:t,confirm:n}=ga.parse(i);return n?(await r.deleteRecord(e,t),u(`Deleted record ${t} from ${e}`,{message:"Record deleted successfully"},["This operation is irreversible"])):{content:[{type:"text",text:JSON.stringify({message:`Deletion not performed. Set 'confirm: true' to delete record '${t}' from '${e}'.`})}]}}case"dataverse_upsert":{let e=fa.parse(i),{entitySetName:t,alternateKey:n,alternateKeyValue:o,alternateKeys:l,data:s,mode:d="upsert"}=e,c;l&&Object.keys(l).length>0&&(c=Object.entries(l).map(([m,f])=>`${v(m)}='${v(f)}'`).join(","));let p=await r.upsertRecord(t,n??"",o??"",s,d,c);return u(`Upsert ${p.operation}: record in ${t}`,{operation:p.operation,id:p.id,message:`Record ${p.operation} successfully`},["Use dataverse_get_entity_key to verify alternate key definitions"])}case"dataverse_assign":{let{entitySetName:e,id:t,ownerType:n,ownerId:o}=ya.parse(i),l=n==="systemuser"?"systemusers":"teams";return await r.updateRecord(e,t,{"ownerid@odata.bind":`/${l}(${o})`}),u(`Assigned record ${t} in ${e} to new owner`,{message:"Record assigned successfully"},["Use dataverse_list_users or dataverse_list_teams to find valid owners"])}default:throw new Error(`Unknown CRUD tool: ${a}`)}}import{z as S}from"zod";var He=[{name:"dataverse_associate",description:"Creates an association between two Dataverse records via a named N:N or 1:N relationship. Requires the relationship schema name obtainable from dataverse_get_relationships. Use for N:N relationships or to link records without modifying a lookup field directly \u2014 for simple 1:N lookups, setting the lookup field in dataverse_update is simpler. WHEN TO USE: Linking two records via an N:N relationship or 1:N navigation property. BEST PRACTICES: Get the relationship schema name from dataverse_get_relationships first; for simple 1:N lookups use dataverse_update. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Source record GUID"},relationshipName:{type:"string",description:"Relationship schema name"},relatedEntitySetName:{type:"string",description:"Related entity set name"},relatedId:{type:"string",description:"Related record GUID"}},required:["entitySetName","id","relationshipName","relatedEntitySetName","relatedId"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_associate_bulk",description:"Associates one source record with multiple related records at once via a named relationship, executing all associations in parallel. Unlike dataverse_associate (one pair at a time), this accepts an array of related IDs and runs them concurrently. Uses Promise.allSettled semantics \u2014 individual failures are reported per item without aborting the others. WHEN TO USE: Bulk N:N association setup (e.g., assigning many privileges to a role, linking many contacts to a campaign). BEST PRACTICES: Check existing associations first with dataverse_query; runs concurrently so order is not guaranteed. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"Source entity set name"},id:{type:"string",description:"Source record GUID"},relationshipName:{type:"string",description:"Relationship schema name"},relatedEntitySetName:{type:"string",description:"Related entity set name"},relatedIds:{type:"array",items:{type:"string"},description:"GUIDs of records to associate (1\u2013200)",minItems:1,maxItems:200}},required:["entitySetName","id","relationshipName","relatedEntitySetName","relatedIds"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_disassociate",description:"Removes an existing association between two Dataverse records on a named relationship. For N:N relationships, provide relatedId and relatedEntitySetName to build the correct $id URL. For 1:N relationships, relatedId and relatedEntitySetName are optional. Use dataverse_get_relationships to find the correct relationship schema name. WHEN TO USE: Removing an N:N or 1:N link between two records without deleting either record. BEST PRACTICES: Get the relationship schema name from dataverse_get_relationships; relatedId is required for N:N. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string"},relationshipName:{type:"string"},relatedId:{type:"string",description:"Required for N:N relationships"},relatedEntitySetName:{type:"string",description:'Entity set name of the related record (required for N:N). E.g., "contacts"'},confirm:{type:"boolean",const:!0,description:"Must be explicitly true to confirm removal of the association"}},required:["entitySetName","id","relationshipName","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_query_associations",description:"Reads existing N:N associations for a record by querying through a navigation property. Returns the related records \u2014 use to verify what's already linked before calling dataverse_associate or dataverse_disassociate. WHEN TO USE: Check if a role already has specific privileges before adding them; check campaign members; check group participants. BEST PRACTICES: Use $select to return only needed columns; set $top to limit results. Example: to check role privileges, query entitySetName='roles', navigationProperty='roleprivileges_association' with $select='name,privilegeid'. WORKFLOW: read_record, inspect_audit.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"Source entity set name, e.g. 'roles'"},id:{type:"string",description:"Source record GUID"},navigationProperty:{type:"string",description:"Navigation property name, e.g. 'roleprivileges_association'"},select:{type:"string",description:"Comma-separated columns to return on related records"},top:{type:"number",description:"Max records to return. Default 50, max 1000"},filter:{type:"string",description:"OData $filter expression applied to related records"}},required:["entitySetName","id","navigationProperty"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],va=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedEntitySetName:h,relatedIds:S.array(S.string().uuid()).min(1).max(200)}),ha=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedEntitySetName:h,relatedId:S.string().uuid()}),ba=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedId:S.string().uuid().optional(),relatedEntitySetName:h.optional(),confirm:S.literal(!0)}),_a=/^[A-Za-z_][A-Za-z0-9_]*$/,wa=S.object({entitySetName:h,id:S.string().uuid(),navigationProperty:S.string().min(1).regex(_a,"Invalid navigation property name"),select:S.string().optional(),top:S.number().int().min(1).max(1e3).optional(),filter:S.string().optional()});async function $e(a,i,r){switch(a){case"dataverse_associate_bulk":{let{entitySetName:e,id:t,relationshipName:n,relatedEntitySetName:o,relatedIds:l}=va.parse(i),s=await Promise.allSettled(l.map(p=>r.associate(e,t,n,o,p))),d=[],c=[];return s.forEach((p,m)=>{p.status==="fulfilled"?d.push(l[m]):c.push({id:l[m],error:p.reason instanceof Error?p.reason.message:String(p.reason)})}),u(`${d.length}/${l.length} associations succeeded`,{entitySetName:e,id:t,relationshipName:n,succeeded:d,failed:c,successCount:d.length,failureCount:c.length},c.length>0?["Some associations failed \u2014 check the 'failed' array for details"]:["All associations processed (Dataverse returns 204 for both new and duplicate associations \u2014 use dataverse_query_associations to verify current state)"])}case"dataverse_associate":{let{entitySetName:e,id:t,relationshipName:n,relatedEntitySetName:o,relatedId:l}=ha.parse(i);return await r.associate(e,t,n,o,l),u(`Associated ${e}(${t}) with ${o}(${l}) via ${n}`,{message:"Records associated successfully"},["Use dataverse_get_relationships to verify relationship names"])}case"dataverse_disassociate":{let{entitySetName:e,id:t,relationshipName:n,relatedId:o,relatedEntitySetName:l}=ba.parse(i);return await r.disassociate(e,t,n,o,l),u(`Disassociated records via ${n}`,{message:"Records disassociated successfully"},["This removes the N:N link but does not delete records"])}case"dataverse_query_associations":{let{entitySetName:e,id:t,navigationProperty:n,select:o,top:l,filter:s}=wa.parse(i),d=`${e}(${t})/${n}`,p=(await r.query(d,{...o?{select:o.split(",").map(m=>m.trim())}:{},top:l??50,...s?{filter:s}:{}})).value??[];return u(`${p.length} associated records via ${n}`,{entitySetName:e,id:t,navigationProperty:n,count:p.length,records:p},["Use dataverse_associate to add new associations","Use dataverse_disassociate to remove existing ones"])}default:throw new Error(`Unknown relation tool: ${a}`)}}import{z as b}from"zod";var Me=[{name:"dataverse_execute_action",description:"Executes a global (unbound) Dataverse action that is not tied to a specific record \u2014 for example WinOpportunity, SendEmail, or custom process actions. Use dataverse_execute_bound_action when the action must operate on a particular record. Actions differ from functions in that they are state-changing operations; for read-only operations use dataverse_execute_function. WHEN TO USE: Invoking global Dataverse actions or custom process actions like WinOpportunity or SendEmail. BEST PRACTICES: Check action parameters via metadata or docs; use dataverse_execute_bound_action for record-scoped actions. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{actionName:{type:"string",description:'Action logical name (e.g., "WinOpportunity")'},parameters:{type:"object",description:"Action parameters"}},required:["actionName"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_execute_function",description:"Executes a global (unbound) Dataverse OData function that is read-only and returns data without side effects \u2014 for example RetrieveTotalRecordCount or InitializeFrom. Use dataverse_execute_action for state-changing operations. Use dataverse_execute_bound_action when the function/action requires a specific record context. WHEN TO USE: Calling global read-only OData functions like RetrieveTotalRecordCount or InitializeFrom. BEST PRACTICES: Functions are side-effect-free and safe to retry; use dataverse_execute_action for mutations. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{functionName:{type:"string",description:'Function name (e.g., "WhoAmI", "RetrieveTotalRecordCount")'},parameters:{type:"object",description:"Function parameters: strings are quoted inline, numbers/booleans inlined as-is, objects serialized as aliased OData parameters"}},required:["functionName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_bound_action",description:"Executes a Dataverse action bound to a specific record instance, passing the entity set name and record GUID as context (e.g., QualifyLead on a lead, or a custom action scoped to an account). The actionName should not include the Microsoft.Dynamics.CRM namespace prefix. Use dataverse_execute_action for global unbound actions that do not require a record context. WHEN TO USE: Running an action scoped to a specific record, e.g. QualifyLead on a lead. BEST PRACTICES: Omit the Microsoft.Dynamics.CRM namespace prefix; ensure the record exists first. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},actionName:{type:"string",description:"Action name (without Microsoft.Dynamics.CRM prefix)"},parameters:{type:"object",description:"Action parameters"}},required:["entitySetName","id","actionName"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_retrieve_dependencies_for_delete",description:"Checks what solution components would block deletion of a specific component. Provide the Dataverse component type code (1=Entity, 2=Attribute, 26=SavedQuery, 29=Workflow, 92=PluginAssembly) and the component GUID. Use before deleting shared Dataverse customization components. WHEN TO USE: Before deleting a solution component to check for blocking dependencies. BEST PRACTICES: Resolve all dependencies before attempting deletion. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{componentType:{type:"number",description:"Dataverse component type code (1=Entity, 2=Attribute, 29=Workflow, 92=PluginAssembly)"},objectId:{type:"string",description:"Component GUID"}},required:["componentType","objectId"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_bound_function",description:"Executes a Dataverse function bound to a specific record (e.g., CalculateRollupField, GetQuantityAvailable). Use for read-only computed operations on a single record. Unlike bound actions, bound functions do not modify data. WHEN TO USE: Calling a read-only computed function on a specific record, e.g. CalculateRollupField. BEST PRACTICES: Functions are side-effect-free and safe to call repeatedly; pass parameters as string key-value pairs. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name of the table (e.g., "accounts")'},id:{type:"string",description:"GUID of the record"},functionName:{type:"string",description:'Name of the bound function (e.g., "CalculateRollupField")'},parameters:{type:"object",description:"Function parameters as key-value pairs of strings",additionalProperties:{type:"string"}}},required:["entitySetName","id","functionName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_dependencies",description:"Lists workflows, Power Automate flows, Business Rules, and custom actions that reference a given table. Use to detect hidden dependencies before modifying or removing a table. Returns component name, type, state (Active/Draft), trigger event (Create/Update/Delete), and count. WHEN TO USE: Before modifying or removing a table, to discover workflows, flows, and plugins that reference it. BEST PRACTICES: Filter by componentType to focus on specific dependency kinds. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{tableName:{type:"string",description:'Logical name of the table to check (e.g., "account", "contact")'},componentType:{type:"array",items:{type:"string",enum:["Workflow","Flow","BusinessRule","Action","BusinessProcessFlow","Plugin","CustomAPI"]},description:"Filter by component type. Default: all types."}},required:["tableName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ne=/^[a-zA-Z0-9_.]+$/,Sa=b.object({actionName:b.string().min(1).regex(ne,"actionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Ta=b.object({functionName:b.string().min(1).regex(ne,"functionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Na=b.object({entitySetName:h,id:b.string().uuid(),actionName:b.string().min(1).regex(ne,"actionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Ra=b.object({componentType:b.number().int().positive(),objectId:b.string().uuid()}),Aa=["Workflow","Flow","BusinessRule","Action","BusinessProcessFlow","Plugin","CustomAPI"],Ea=b.object({tableName:b.string().min(1),componentType:b.array(b.enum(Aa)).optional()}),xa=b.object({entitySetName:h,id:b.string().uuid(),functionName:b.string().min(1).regex(ne,"functionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})});async function We(a,i,r){switch(a){case"dataverse_execute_action":{let{actionName:e,parameters:t}=Sa.parse(i),n=await r.executeAction(e,t);return u(`Executed unbound action ${e}`,n,["Use dataverse_list_custom_actions to discover available actions"])}case"dataverse_execute_function":{let{functionName:e,parameters:t}=Ta.parse(i),n=await r.executeFunction(e,t);return u(`Executed function ${e}`,n,["Functions are read-only operations"])}case"dataverse_execute_bound_action":{let{entitySetName:e,id:t,actionName:n,parameters:o}=Na.parse(i),l=await r.executeBoundAction(e,t,n,o);return u(`Executed bound action ${n} on ${e}(${t})`,l)}case"dataverse_list_dependencies":{let{tableName:e,componentType:t}=Ea.parse(i),n=await r.listTableDependencies(e,t);return u(`${n.count} dependencies found for component`,n,["Use this information before deleting or modifying the component"])}case"dataverse_retrieve_dependencies_for_delete":{let{componentType:e,objectId:t}=Ra.parse(i),n=await r.listDependencies(e,t),o=Array.isArray(n)?n:n.value??[n];return u(`${o.length} dependencies found`,n,["Review dependencies before deleting the component"])}case"dataverse_execute_bound_function":{let{entitySetName:e,id:t,functionName:n,parameters:o}=xa.parse(i),l=await r.executeBoundFunction(e,t,n,o);return u(`Executed bound function ${n} on ${e}(${t})`,l)}default:throw new Error(`Unknown action tool: ${a}`)}}import{z as $}from"zod";var je=[{name:"dataverse_batch_execute",description:`Executes multiple Dataverse operations in a single HTTP $batch request to reduce network round-trips and improve throughput. Accepts up to 1000 individual GET, POST, PATCH, or DELETE requests. Use for bulk creates, updates, or deletes that need to be grouped for performance. Set useChangeset=true to wrap all mutating operations (POST/PATCH/DELETE) in an atomic changeset \u2014 a failure rolls back ALL changeset operations. Individual per-operation results (status, body) are returned as an array in the same order as the input requests. $REF OPERATIONS (N:N/1:N associations): POST to a collection-valued navigation property (N:N): url='roles({id})/roleprivileges_association/$ref', body must contain { "@odata.id": "https://<org_url>/api/data/v9.2/privileges({privilegeId})" } with an ABSOLUTE URL \u2014 a relative URL will cause a 400 error. PUT to a single-valued navigation property (lookup/1:N): url='contacts({id})/account_primary_contact/$ref', body must contain { "@odata.id": "https://<org_url>/api/data/v9.2/accounts({accountId})" } with an ABSOLUTE URL. Example: { method: 'POST', url: 'roles({roleId})/roleprivileges_association/$ref', body: { '@odata.id': 'https://org.crm.dynamics.com/api/data/v9.2/privileges({privilegeId})' } }. WHEN TO USE: Bulk creates, updates, or deletes (up to 1000 ops) needing a single HTTP round-trip; batched $ref associations. BEST PRACTICES: Use useChangeset=true for atomic all-or-nothing mutations; batch GETs for parallel reads; always use absolute URLs in @odata.id for $ref bodies. WORKFLOW: bulk_operations.`,inputSchema:{type:"object",properties:{requests:{type:"array",description:"Array of batch requests to execute",items:{type:"object",properties:{method:{type:"string",enum:["GET","POST","PATCH","DELETE"],description:"HTTP method"},url:{type:"string",description:'Relative URL (e.g., "accounts(guid)" or "contacts"). For $ref operations (e.g. "roles({id})/roleprivileges_association/$ref"), the request body MUST include "@odata.id" with an ABSOLUTE URL \u2014 relative URLs in @odata.id cause 400 errors.'},body:{type:"object",description:"Request body for POST/PATCH operations"}},required:["method","url"]}},useChangeset:{type:"boolean",description:"Wrap mutating operations (POST/PATCH/DELETE) in an atomic changeset. A failure rolls back ALL operations in the changeset. Defaults to false."}},required:["requests"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}}],ka=$.object({method:$.enum(["GET","POST","PATCH","DELETE"]),url:$.string().min(1).refine(a=>!/[\r\n]/.test(a),{message:"Batch URL must not contain CR or LF characters"}).refine(a=>!a.startsWith("http"),{message:"Batch URL must be a relative path, not an absolute URL"}).refine(a=>!/(\.\.[\/\\])|(^\.\.$)/.test(a),{message:"Batch URL must not contain path traversal sequences"}),body:$.record($.unknown()).optional()}),Ia=$.object({requests:$.array(ka).min(1).max(1e3),useChangeset:$.boolean().default(!1).describe("Wrap mutating operations (POST/PATCH/DELETE) in an atomic changeset. A failure rolls back ALL operations in the changeset.")});async function Fe(a,i,r,e){if(a==="dataverse_batch_execute"){let{requests:t,useChangeset:n}=Ia.parse(i),o=t.map(d=>({method:d.method,url:d.url,body:d.body}));await e?.report(0,t.length);let l=await r.batchExecute(o,n);await e?.report(t.length,t.length);let s=l.filter(d=>d===null||!d.error).length;return u(`Batch executed: ${s}/${l.length} operations succeeded`,{results:l,count:l.length},["Use batch for bulk create/update operations to reduce HTTP round-trips"])}throw new Error(`Unknown batch tool: ${a}`)}import{z as re}from"zod";var Be=[{name:"dataverse_change_detection",description:"Detects new, modified, and deleted records since a previous sync using Dataverse change tracking (delta queries). On first call, pass deltaToken=null to get an initial snapshot and receive a token. On subsequent calls, pass the returned token to retrieve only changes since last sync. Change tracking must be enabled on the table in Dataverse settings. Returns newAndModified records, deleted record IDs, and the nextDeltaToken for the next call. WHEN TO USE: Incremental sync \u2014 detecting records created, updated, or deleted since a previous snapshot. BEST PRACTICES: Store the deltaToken persistently; always specify $select to minimize payload. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name (e.g., "accounts")'},deltaToken:{anyOf:[{type:"string"},{type:"null"}],description:"Delta token from a previous call, or null for the initial sync"},select:{type:"array",items:{type:"string"},description:"Columns to return (recommended to minimise payload)"}},required:["entitySetName","deltaToken"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Da=re.object({entitySetName:h,deltaToken:re.string().nullable(),select:re.array(re.string()).optional()});async function Ge(a,i,r){if(a==="dataverse_change_detection"){let{entitySetName:e,deltaToken:t,select:n}=Da.parse(i),o;try{o=await r.getChangedRecords(e,t,n)}catch(s){let d=s instanceof Error?s.message:String(s);if(/change.?track|0x80072491/i.test(d))return X({type:"feature_disabled",feature:"Change Tracking",cannotProceedBecause:`Change tracking is not enabled on '${e}', so delta queries cannot be executed.`,adminPortal:"Power Apps Maker Portal",steps:["Open Power Apps maker portal (make.powerapps.com)",`Navigate to Tables \u2192 search for '${e}'`,"Open the table \u2192 click the Settings (gear) icon",'Enable "Track changes"',"Save the table, then publish customizations"],fixableViaToolName:"dataverse_update_entity"});throw s}let l=Array.isArray(o?.newAndModified)?o.newAndModified:[];return u(`${l.length} changed records detected`,o,["Store the returned deltaToken for subsequent incremental sync"])}throw new Error(`Unknown tracking tool: ${a}`)}import{z as M}from"zod";var Oa=M.object({components:M.object({entities:M.array(M.string()).optional().describe("Entity logical names to publish"),webResources:M.array(M.string()).optional().describe("Web resource names to publish"),optionSets:M.array(M.string()).optional().describe("Global OptionSet names to publish")}).optional().describe("Specific components to publish. If omitted, ALL unpublished customizations are published.")}),ze=[{name:"dataverse_publish_customizations",description:'Publishes unpublished Dataverse customizations. \u26A0\uFE0F WITHOUT components \u2192 calls PublishAllXml, an EXCLUSIVE ORG-WIDE LOCK lasting 30-120s. While active, ALL other publish calls (scoped or global) fail with "already in progress". \u2705 ALWAYS prefer specifying components.entities/webResources/optionSets to use the scoped PublishXml action instead \u2014 faster and does NOT block other operations. \u274C DO NOT call without components after each individual change \u2014 batch ALL your schema changes first, then publish ONCE at the end. If you receive a "publish already in progress" error, the MCP will auto-retry every 30s (up to 2 minutes) \u2014 do NOT call the tool again; wait for the previous call to return. WHEN TO USE: Once at the end of a series of schema changes (table/column/relationship/form creation). BEST PRACTICES: Pass components.entities with the list of modified entity logical names. Full publish only if you cannot enumerate the changed components. WORKFLOW: manage_solution.',inputSchema:{type:"object",properties:{components:{type:"object",description:"Specific components. Omit to publish all.",properties:{entities:{type:"array",items:{type:"string"}},webResources:{type:"array",items:{type:"string"}},optionSets:{type:"array",items:{type:"string"}}}}},required:[]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}];async function Ve(a,i,r){switch(a){case"dataverse_publish_customizations":{let{components:e}=Oa.parse(i??{}),t=await r.publishCustomizations(e);return u("Customizations published successfully",t,["Changes are now visible to all users in the environment"])}default:throw new Error(`Unknown solution tool: ${a}`)}}import{z as Z}from"zod";var Ca=Z.object({callerId:Z.string().uuid().describe('Azure AD Object ID (GUID) of the Dataverse system user to impersonate. Requires the executing account to have the "Act on behalf of another user" privilege in Dataverse.'),toolName:Z.string().min(1).describe('Name of the MCP tool to execute on behalf of the user (e.g., "dataverse_create", "dataverse_query")'),toolArgs:Z.record(Z.unknown()).describe("Arguments for the wrapped tool, as an object")}),ue=[{name:"dataverse_impersonate",description:'Executes another Dataverse tool on behalf of a different system user by injecting the MSCRMCallerId header. Requires the executing account to have the "Act on behalf of another user" privilege in Dataverse. The impersonation applies ONLY to the single tool call specified. Use for auditing workflows that must create or update records under a specific user identity. WHEN TO USE: Creating or updating records under a specific user identity for audit-trail purposes. BEST PRACTICES: Impersonation applies to the single wrapped tool call only; cannot impersonate System Administrators. WORKFLOW: update_record.',inputSchema:{type:"object",properties:{callerId:{type:"string",description:"GUID (Azure AD Object ID) of the Dataverse system user to impersonate"},toolName:{type:"string",description:'MCP tool to execute while impersonating (e.g., "dataverse_create")'},toolArgs:{type:"object",description:"Arguments for the wrapped tool"}},required:["callerId","toolName","toolArgs"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}}];async function Ke(a,i,r,e){if(a!=="dataverse_impersonate")throw new Error(`Unknown impersonate tool: ${a}`);let{callerId:t,toolName:n,toolArgs:o}=Ca.parse(i);try{if(((await r.query(`systemusers(${v(t)})/systemuserroles_association`,{select:["name"],filter:"name eq 'System Administrator'",top:1})).value?.length??0)>0)throw new Error("Security policy: impersonation of users with System Administrator role is prohibited to prevent privilege escalation.")}catch(d){throw d instanceof Error&&d.message.includes("Security policy")?d:new Error(`Security policy: cannot verify callerId roles \u2014 impersonation denied. Cause: ${String(d)}`)}let l=r.http,s=l.defaultHeaders.MSCRMCallerId;try{l.defaultHeaders.MSCRMCallerId=t;let d=await e(n,o,r);return u(`Impersonated as ${t}, executed ${n}`,{impersonatedAs:t,tool:n,result:JSON.parse(d.content[0].text)},["Impersonation applies to this single call only"])}finally{s===void 0?delete l.defaultHeaders.MSCRMCallerId:l.defaultHeaders.MSCRMCallerId=s}}import{z as A}from"zod";var Ua={10:"Pre-validation",20:"Pre-operation",40:"Post-operation",45:"Post-operation (deprecated)"},La={0:"Synchronous",1:"Asynchronous"},Pa=A.object({workflowId:A.string().uuid(),activate:A.boolean()}),Xe=[{name:"dataverse_list_custom_actions",description:"Lists all custom actions (custom API / SDK messages) registered in the environment. Returns the message name, category, bound entity (if any), execute privilege, and whether it is customizable. Useful for discovering available automation entry points and agent-callable actions. WHEN TO USE: Discovering available custom API / SDK messages for automation. BEST PRACTICES: Use nameFilter to search; follow up with dataverse_execute_action to invoke. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 100, max 500)"},nameFilter:{type:"string",description:"Filter by name (substring match)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_plugin_steps",description:"Lists plugin steps (SdkMessageProcessingStep registrations) in the environment. Shows plugin assembly, step name, message (Create/Update/Delete/\u2026), entity, stage (pre/post), mode (sync/async), and state (enabled/disabled). Essential for understanding what custom business logic fires on Dataverse operations. WHEN TO USE: Understanding what custom business logic fires on CRUD operations for a table. BEST PRACTICES: Filter by entityLogicalName; check stage and mode to understand execution order and timing. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 100, max 500)"},activeOnly:{type:"boolean",description:"Return only enabled steps (default: true)"},entityLogicalName:{type:"string",description:"Filter by entity logical name (e.g. 'account')"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_set_workflow_state",description:"Activates or deactivates a Dataverse workflow (classic workflow / real-time workflow / action). Set activate=true to activate (statecode 1, statuscode 2) or activate=false to deactivate (statecode 0, statuscode 1). Returns the new state. WHEN TO USE: Activating or deactivating a classic workflow, real-time workflow, or action. BEST PRACTICES: Verify workflow ID first; deactivation stops future triggers but does not cancel in-progress runs. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"The workflow GUID"},activate:{type:"boolean",description:"true = activate, false = deactivate (draft)"}},required:["workflowId","activate"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_connection_references",description:"Lists all Power Automate connection references defined in the environment, showing their display name, connector ID, connection ID, and active status. Use this before importing a solution containing flows to detect broken or unmapped connections that would cause silent import failures. WHEN TO USE: Pre-deployment solution validation, auditing connection health, identifying broken flow connections. BEST PRACTICES: Look for inactive (isActive=false) references \u2014 these indicate flows that will fail after deployment. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{connectorId:{type:"string",description:"Filter by connector ID substring (e.g. '/providers/Microsoft.PowerApps/apis/shared_sharepointonline')"},activeOnly:{type:"boolean",description:"Return only active connection references (default: false = return all)"},top:{type:"number",description:"Maximum records to return (default 100, max 500)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],qa=A.object({top:A.number().positive().max(500).optional().default(100),nameFilter:A.string().optional()}),Ha=A.object({top:A.number().positive().max(500).optional().default(100),activeOnly:A.boolean().optional().default(!0),entityLogicalName:A.string().optional()}),$a=A.object({connectorId:A.string().optional(),activeOnly:A.boolean().optional().default(!1),top:A.number().int().positive().max(500).optional().default(100)});async function Qe(a,i,r){switch(a){case"dataverse_list_custom_actions":{let{top:e,nameFilter:t}=qa.parse(i??{}),n=["isprivate eq false"];t&&n.push(`contains(name,'${v(t)}')`);let l=(await r.query("sdkmessages",{select:["sdkmessageid","name","categoryname","isprivate","isreadonly","isvalidforexecuteasync"],filter:n.join(" and "),top:e})).value.map(s=>({id:s.sdkmessageid,name:s.name,category:s.categoryname??"",isPrivate:s.isprivate,isReadOnly:s.isreadonly,asyncSupported:s.isvalidforexecuteasync}));return u(`${l.length} custom actions found`,{total:l.length,messages:l},["Use dataverse_execute_action to run an action"])}case"dataverse_list_plugin_steps":{let{top:e,activeOnly:t,entityLogicalName:n}=Ha.parse(i??{}),o={select:["sdkmessageprocessingstepid","name","stage","mode","rank","statecode","filteringattributes","asyncautodelete"],expand:"sdkmessageid($select=name,categoryname),plugintypeid($select=name,assemblyname),sdkmessagefilterid($select=primaryobjecttypecode)",top:e};t&&(o.filter="statecode eq 0");let s=(await r.query("sdkmessageprocessingsteps",o)).value;if(n){let c=n.toLowerCase();s=s.filter(p=>p.sdkmessagefilterid?.primaryobjecttypecode?.toLowerCase()===c)}let d=s.map(c=>({id:c.sdkmessageprocessingstepid,name:c.name,message:c.sdkmessageid?.name??"",entity:c.sdkmessagefilterid?.primaryobjecttypecode??"",assembly:c.plugintypeid?.assemblyname??"",pluginType:c.plugintypeid?.name??"",stage:c.stage,stageName:Ua[c.stage]??`Stage ${c.stage}`,mode:c.mode,modeName:La[c.mode]??`Mode ${c.mode}`,rank:c.rank,isActive:c.statecode===0,filteringAttributes:c.filteringattributes??null,asyncAutoDelete:c.asyncautodelete}));return u(`${d.length} plugin steps found`,{total:d.length,steps:d},["Use dataverse_get_plugin_trace_logs for debugging plugin issues"])}case"dataverse_set_workflow_state":{let{workflowId:e,activate:t}=Pa.parse(i);try{await r.updateRecord("workflows",e,{statecode:t?1:0,statuscode:t?2:1})}catch(n){let o=n instanceof Error?n.message:String(n);throw/0x80040203|does not refer to a valid workflow|404/i.test(o)?new Error(`Workflow '${e}' not found. Use dataverse_list_workflows to retrieve valid workflow GUIDs in this environment. Original error: ${o}`):n}return u(`Workflow state updated for ${e}`,{workflowId:e,newState:t?"Activated":"Draft",statecode:t?1:0,statuscode:t?2:1})}case"dataverse_list_connection_references":{let{connectorId:e,activeOnly:t,top:n}=$a.parse(i??{}),o=[];t&&o.push("statecode eq 0"),e&&o.push(`contains(connectorid,'${v(e)}')`);let l={select:["connectionreferenceid","connectionreferencelogicalname","connectionreferencedisplayname","connectorid","connectionid","statecode","statuscode"],orderby:"connectionreferencedisplayname asc",top:n};o.length&&(l.filter=o.join(" and "));let d=((await r.query("connectionreferences",l)).value??[]).map(m=>({id:m.connectionreferenceid??"",logicalName:m.connectionreferencelogicalname??"",displayName:m.connectionreferencedisplayname??"",connectorId:m.connectorid??"",connectionId:m.connectionid??null,isActive:m.statecode===0,statusCode:m.statuscode??null})),c=d.filter(m=>!m.isActive).length,p=c>0?`${d.length} connection references found (${c} inactive \u2014 check before deploying)`:`${d.length} connection references found`;return u(p,{connectionReferences:d,count:d.length,inactiveCount:c},["Inactive references (isActive=false) indicate broken connections that will cause flow failures","Use the Power Apps maker portal to reconnect or replace broken connection references before deploying"])}default:throw new Error(`Unknown customization tool: ${a}`)}}import{z as x}from"zod";var Ma={1e8:"String",100000001:"Number",100000002:"Boolean",100000003:"JSON",100000004:"DataSource"},Wa={String:1e8,Integer:100000001,Boolean:100000002,JSON:100000003},Ye=[{name:"dataverse_get_environment_variable",description:"Retrieves an environment variable's definition and current value from Dataverse. Returns the schema name, display name, type, default value, and the current override value (if set). Useful for reading feature flags, configuration values, and integration settings stored in Dataverse environment variables. WHEN TO USE: Reading configuration values, feature flags, or integration settings stored as environment variables. BEST PRACTICES: Check both defaultValue and currentValue; the effective value is currentValue ?? defaultValue. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"The schema name of the environment variable (e.g. 'new_MyConfig')"}},required:["schemaName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_set_environment_variable",description:"Sets or updates an environment variable value in Dataverse. If a value record already exists for the variable, it is updated; otherwise a new value record is created. The schemaName must match an existing environment variable definition. WHEN TO USE: Updating configuration values or feature flags stored in Dataverse environment variables. BEST PRACTICES: Verify the variable exists first with dataverse_get_environment_variable; schemaName must match exactly. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"The schema name of the environment variable"},value:{type:"string",description:"The new value to set"}},required:["schemaName","value"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_create_environment_variable",description:"Creates a new Dataverse environment variable definition and sets its initial value. Use this when the variable does not yet exist \u2014 use dataverse_set_environment_variable to update an existing one. WHEN TO USE: Initial setup of configuration flags, feature toggles, or integration settings. BEST PRACTICES: Use a solution-prefixed schemaName (e.g. 'myprefix_MyConfig'); provide solutionUniqueName to register in a solution. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"Schema name of the new variable (e.g. 'new_MyConfig'). Case-sensitive."},displayName:{type:"string",description:"Human-readable display name"},type:{type:"string",enum:["String","Integer","Boolean","JSON"],description:"Variable type"},value:{type:"string",description:"Initial value to set"},description:{type:"string",description:"Optional description"},defaultValue:{type:"string",description:"Optional default value (fallback when no override value is set)"},confirm:{type:"boolean",description:"Must be true \u2014 confirms intentional creation of a new environment variable definition"}},required:["schemaName","displayName","type","value","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_environment_capabilities",description:"Returns a comprehensive snapshot of the Dataverse environment: identity (WhoAmI), organization settings (name, version, language, audit config), unmanaged solution count, and environment variable count. Use at the start of a session to orient yourself to the environment and its configuration. WHEN TO USE: Beginning of any Dataverse automation session, before bulk operations, or when debugging unexpected behavior. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ja=x.object({schemaName:x.string().min(1)}),Fa=x.object({schemaName:x.string().min(1),value:x.string()}),Ba=x.object({schemaName:x.string().min(1),displayName:x.string().min(1),type:x.enum(["String","Integer","Boolean","JSON"]),value:x.string(),description:x.string().optional(),defaultValue:x.string().optional(),confirm:x.literal(!0,{errorMap:()=>({message:"Set confirm: true to create a new environment variable definition"})})});async function Ga(a,i){let{schemaName:r}=ja.parse(a),t=(await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid","schemaname","displayname","description","type","defaultvalue","isrequired"],top:1})).value;if(t.length===0)throw new Error(`Environment variable '${r}' not found. Check the schema name (it is case-sensitive, e.g. 'new_MyConfig'). To browse existing variables: Power Apps maker portal \u2192 Solutions \u2192 your solution \u2192 Environment Variables. To create a new one: open a solution \u2192 New \u2192 More \u2192 Environment Variable.`);let n=t[0],o=n.environmentvariabledefinitionid,s=(await i.query("environmentvariablevalues",{filter:`_environmentvariabledefinitionid_value eq ${o}`,select:["environmentvariablevalueid","value"],top:1})).value,d=s.length>0?s[0]:null,c=n.type,p=d?d.value??null:null,m=n.defaultvalue??null,f={schemaName:n.schemaname,displayName:n.displayname??"",description:n.description??"",type:c,typeName:Ma[c]??"Unknown",defaultValue:m,currentValue:p,valueId:d?d.environmentvariablevalueid:null,isRequired:n.isrequired??!1,effectiveValue:p??m};return u(`Environment variable '${f.schemaName}': ${f.typeName} = ${f.effectiveValue??"(not set)"}`,f,["Use dataverse_set_environment_variable to update the value"])}async function za(a,i){let{schemaName:r,value:e}=Fa.parse(a),n=(await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid","schemaname"],top:1})).value;if(n.length===0)throw new Error(`Environment variable definition '${r}' not found. This tool can only update the value of an existing variable. To create a new environment variable: Power Apps maker portal \u2192 Solutions \u2192 your solution \u2192 New \u2192 More \u2192 Environment variable. Then call this tool to set its value.`);let o=n[0].environmentvariabledefinitionid,s=(await i.query("environmentvariablevalues",{filter:`_environmentvariabledefinitionid_value eq ${o}`,select:["environmentvariablevalueid","value"],top:1})).value,d,c;s.length>0?(c=s[0].environmentvariablevalueid,await i.updateRecord("environmentvariablevalues",c,{value:e}),d="updated"):(c=await i.createRecord("environmentvariablevalues",{value:e,"EnvironmentVariableDefinitionId@odata.bind":`/environmentvariabledefinitions(${o})`}),d="created");let p={schemaName:r,operation:d,valueId:c,value:e};return u(`Environment variable '${r}' set to new value`,p,["Use dataverse_get_environment_variable to verify the update"])}async function Va(a,i){let{schemaName:r,displayName:e,type:t,value:n,description:o,defaultValue:l}=Ba.parse(a),s=q({toolName:"dataverse_create_environment_variable"}).map(N=>`[${N.severity.toUpperCase()}] ${N.code}: ${N.message}`);if((await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid"],top:1})).value.length>0)throw new Error(`Environment variable '${r}' already exists. Use dataverse_set_environment_variable to update its value.`);let c=Wa[t]??1e8,p={schemaname:r,displayname:e,type:c};o&&(p.description=o),l!==void 0&&(p.defaultvalue=l);let m=await i.createRecord("environmentvariabledefinitions",p),f=await i.createRecord("environmentvariablevalues",{value:n,"EnvironmentVariableDefinitionId@odata.bind":`/environmentvariabledefinitions(${m})`});return u(`Environment variable '${r}' created (type: ${t}, value: '${n}').`,{schemaName:r,displayName:e,type:t,typeCode:c,definitionId:m,valueId:f,value:n,...s.length>0&&{warnings:s}},["Use dataverse_get_environment_variable to verify","Use dataverse_set_environment_variable to update the value later"])}async function Je(a,i,r){switch(a){case"dataverse_get_environment_variable":return Ga(i,r);case"dataverse_set_environment_variable":return za(i,r);case"dataverse_create_environment_variable":return Va(i,r);case"dataverse_environment_capabilities":{let[e,t,n,o]=await Promise.allSettled([r.executeFunction("WhoAmI",{}),r.query("organizations",{select:["uniquename","friendlyname","version","languagecode","isauditenabled"],top:1}),r.query("solutions",{filter:"ismanaged eq false",select:["uniquename"]}),r.query("environmentvariabledefinitions",{select:["schemaname"],top:1})]),l=e.status==="fulfilled"?e.value:null,s=t.status==="fulfilled"?t.value.value:[],d=n.status==="fulfilled"?n.value.value:[],c=o.status==="fulfilled"?o.value.value:[];return u("Environment capabilities retrieved",{identity:l,organization:s[0]??null,unmanagedSolutionsCount:d.length,environmentVariablesCount:c.length},["Use dataverse_list_solutions for full solution list","Use dataverse_get_environment_variable to read specific env vars"])}default:throw new Error(`Unknown environment tool: ${a}`)}}import{z as L}from"zod";var Ka={0:"Execute",1:"Create",2:"Retrieve",3:"RetrieveMultiple",4:"GetParent",5:"Update",6:"Delete",7:"Assign"},Xa={0:"Waiting",10:"WaitingForResources",20:"InProgress",21:"Pausing",22:"Canceling",30:"Succeeded",31:"Failed",32:"Canceled"},Qa={0:"ReadyToRun",1:"Suspended",2:"Locked",3:"Completed"},Ya=["plugintracelogid","typename","messagename","primaryentity","depth","operationtype","exceptiondetails","messageblock","createdon","performanceexecutionduration","correlationid","requestid"],Ja=["asyncoperationid","name","operationtype","statuscode","statecode","message","createdon","startedon","completedon"],Ze=[{name:"dataverse_get_plugin_trace_logs",description:"Retrieves recent plugin and custom workflow activity trace logs from Dataverse. Shows execution details including plugin type name, triggering message, entity, execution duration, trace messages written by the developer, and exception details if the plugin failed. Requires the Plugin Trace Log feature to be enabled in Dataverse settings (Settings > Administration > System Settings > Customization tab > 'Enable logging to plugin trace log'). Essential for debugging plugin failures in production. WHEN TO USE: Debugging plugin failures or performance issues in production. BEST PRACTICES: Enable Plugin Trace Log in Dataverse settings first; filter by plugin name or entity to narrow results. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records to return (default 50, max 200)"},pluginTypeFilter:{type:"string",description:"Filter by plugin type name (substring match, e.g. 'AccountValidation')"},messageFilter:{type:"string",description:"Filter by message name (e.g. 'Create', 'Update')"},entityFilter:{type:"string",description:"Filter by entity logical name (e.g. 'account')"},exceptionsOnly:{type:"boolean",description:"Return only traces where an exception occurred (default false)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_workflow_trace_logs",description:"Retrieves background workflow (Power Automate classic / legacy workflow engine) execution records from Dataverse. These are the AsyncOperation records for workflow-type operations, useful for diagnosing failures in background workflows and real-time workflows running asynchronously. Note: For modern cloud flows (Power Automate), use the Power Automate portal instead. WHEN TO USE: Diagnosing failures in classic/legacy background workflows. BEST PRACTICES: Use failedOnly=true to focus on errors; for modern cloud flows use Power Automate portal. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 50, max 200)"},failedOnly:{type:"boolean",description:"Return only failed workflows (statuscode eq 31, default false)"},entityFilter:{type:"string",description:"Filter by regarding entity type (e.g. 'account')"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Za=L.object({top:L.number().int().positive().max(200).optional().default(50),pluginTypeFilter:L.string().optional(),messageFilter:L.string().optional(),entityFilter:L.string().optional(),exceptionsOnly:L.boolean().optional().default(!1)}),en=L.object({top:L.number().int().positive().max(200).optional().default(50),failedOnly:L.boolean().optional().default(!1),entityFilter:L.string().optional()});function U(a){return typeof a=="string"?a:""}function tn(a){return typeof a=="number"?a:null}async function et(a,i,r){switch(a){case"dataverse_get_plugin_trace_logs":{let e=Za.parse(i),t=[];e.pluginTypeFilter&&t.push(`contains(typename,'${v(e.pluginTypeFilter)}')`),e.messageFilter&&t.push(`messagename eq '${v(e.messageFilter)}'`),e.entityFilter&&t.push(`primaryentity eq '${v(e.entityFilter)}'`),e.exceptionsOnly&&t.push("exceptiondetails ne null");let n=t.length>0?t.join(" and "):void 0,s=((await r.query("plugintracelogs",{select:Ya,orderby:"createdon desc",top:e.top,...n!==void 0?{filter:n}:{}})).value??[]).map(c=>{let p=typeof c.operationtype=="number"?c.operationtype:0,m=c.exceptiondetails;return{id:U(c.plugintracelogid),typeName:U(c.typename),message:U(c.messagename),entity:U(c.primaryentity),depth:typeof c.depth=="number"?c.depth:0,operationType:p,operationTypeName:Ka[p]??String(p),createdOn:U(c.createdon),durationMs:tn(c.performanceexecutionduration),correlationId:U(c.correlationid),requestId:U(c.requestid),hasException:m!=null&&m!=="",exceptionDetails:typeof m=="string"?m:null,messageBlock:typeof c.messageblock=="string"?c.messageblock:null}}),d={total:s.length,logs:s};return u(`${s.length} plugin trace logs found`,d,["Filter by plugin name or correlation ID for specific traces"])}case"dataverse_get_workflow_trace_logs":{let e=en.parse(i),t=["operationtype eq 10"];e.failedOnly&&t.push("statuscode eq 31");let n=t.join(" and "),s=((await r.query("asyncoperations",{select:Ja,filter:n,orderby:"createdon desc",top:e.top})).value??[]).map(c=>{let p=typeof c.statuscode=="number"?c.statuscode:0,m=typeof c.statecode=="number"?c.statecode:0,f=c.message;return{id:U(c.asyncoperationid),name:U(c.name),statusCode:p,statusName:Xa[p]??String(p),stateCode:m,stateName:Qa[m]??String(m),createdOn:U(c.createdon),startedOn:typeof c.startedon=="string"?c.startedon:null,completedOn:typeof c.completedon=="string"?c.completedon:null,errorMessage:typeof f=="string"?f:null}}),d={total:s.length,workflows:s};return u(`${s.length} workflow trace logs found`,d,["Filter by status or entity type for specific workflow traces"])}default:throw new Error(`Unknown trace tool: ${a}`)}}import{z as E}from"zod";var tt=[{name:"dataverse_search",description:"Full-text Relevance Search across all configured Dataverse tables. Returns ranked results with entity name, record ID, score, and matched fields. Requires Relevance Search to be enabled in Dataverse admin settings. Use when you need to find records without knowing which table they belong to. WHEN TO USE: Cross-table free-text search when you don't know which table contains the data. BEST PRACTICES: Narrow with entities[] and $filter; use searchType=full for Lucene operators (AND, OR, NOT, wildcards). WORKFLOW: search_data.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Full-text search string (supports Lucene syntax with searchType=full)"},entities:{type:"array",items:{type:"string"},description:"Restrict to specific table logical names (omit to search all configured tables). Required when using select."},top:{type:"number",description:"Max results (default 10, max 50)"},searchMode:{type:"string",enum:["any","all"],description:"Match any or all terms (default: any)"},searchType:{type:"string",enum:["simple","full"],description:"Search syntax: simple (default) or full (enables Lucene syntax: AND, OR, NOT, wildcards, fuzzy, proximity, regex)"},filter:{type:"string",description:'OData $filter to apply on search results (e.g., "statecode eq 0")'},facets:{type:"array",items:{type:"string"},description:'Fields to return faceted counts for (e.g., ["entityname,count:100","statecode"])'},orderby:{type:"array",items:{type:"string"},description:'Result sort order (e.g., ["@search.score desc","modifiedon desc"])'},select:{type:"array",items:{type:"string"},description:"Columns to return per result. Only applies when entities is also specified."}},required:["query"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],an=E.object({query:E.string().min(1),entities:E.array(E.string()).optional(),top:E.number().int().positive().max(50).optional().default(10),searchMode:E.enum(["any","all"]).optional().default("any"),searchType:E.enum(["simple","full"]).optional(),filter:E.string().optional(),facets:E.array(E.string()).optional(),orderby:E.array(E.string()).optional(),select:E.array(E.string()).optional()});async function at(a,i,r){switch(a){case"dataverse_search":{let e=an.parse(i),t={search:e.query,top:e.top};if(e.entities?.length){let s=e.entities.map(d=>({name:d,...e.select?.length?{selectColumns:e.select}:{}}));t.entities=JSON.stringify(s)}e.searchType==="full"&&(t.options=JSON.stringify({querytype:"lucene"})),e.filter&&(t.filter=e.filter),e.facets?.length&&(t.facets=JSON.stringify(e.facets)),e.orderby?.length&&(t.orderby=JSON.stringify(e.orderby));let n;try{n=await r.search(t)}catch(s){let d=s instanceof Error?s.message:String(s);if(/404|not.?found|search.{0,20}disabl|relevance.?search|0x80048[Dd]0[Bb]|search.{0,20}not.{0,20}config|search.{0,20}unavail/i.test(d))return X({type:"feature_disabled",feature:"Dataverse Search (Relevance Search)",cannotProceedBecause:"Relevance Search is not enabled for this Dataverse environment, so full-text cross-table search is unavailable.",adminPortal:"Power Platform Admin Center",steps:["Open Power Platform Admin Center (admin.powerplatform.microsoft.com)","Select your environment \u2192 Settings","Navigate to Product \u2192 Features",'Under "Search", toggle "Dataverse Search" to On',"Save \u2014 indexing may take a few minutes before search is available"]});throw s}let o=(n.value??[]).map(s=>({entityName:s.entityname??"",objectId:s.objectid??"",score:s.score??0,highlights:s.highlights??{},fields:s.attributes??{}})),l={totalRecordCount:n.totalrecordcount??0,results:o};return n.facets&&(l.facets=n.facets),u(`${o.length} search results for '${e.query}'`,l,["Use dataverse_get to retrieve full record details","Narrow results with entities[] filter"])}default:throw new Error(`Unknown search tool: ${a}`)}}import{z as W}from"zod";var pe={1:"Create",2:"Update",3:"Delete",4:"Activate",5:"Deactivate",11:"Share",12:"Unshare",13:"Assign",104:"Access"},nn=Object.fromEntries(Object.entries(pe).map(([a,i])=>[i,Number(a)])),nt=[{name:"dataverse_get_audit_log",description:"Retrieves audit log entries from Dataverse. Returns operation details, user info, and parsed change data for each entry. At least one filter (recordId, entityLogicalName, userId, fromDate, or operations) is recommended to avoid large result sets. Audit must be enabled on the environment and table \u2014 returns a clear error if auditing is disabled (HTTP 403). WHEN TO USE: Tracking who changed what and when on Dataverse records. BEST PRACTICES: Always provide at least one filter; audit must be enabled on the table. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{recordId:{type:"string",description:"GUID of a specific record to retrieve audit entries for"},entityLogicalName:{type:"string",description:'Logical name of the entity to filter audit entries (e.g., "account", "contact")'},userId:{type:"string",description:"GUID of the user who made the changes"},fromDate:{type:"string",description:"ISO 8601 date string \u2014 only return audit entries created on or after this date"},top:{type:"number",description:"Maximum number of audit entries to return (default: 50, max: 500)"},operations:{type:"array",items:{type:"string"},description:'Filter by operation names: "Create", "Update", "Delete", "Activate", "Deactivate", "Share", "Unshare", "Assign", "Access"'}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],rn=W.object({recordId:W.string().uuid().optional(),entityLogicalName:W.string().min(1).optional(),userId:W.string().uuid().optional(),fromDate:W.string().datetime({offset:!0}).optional(),top:W.number().positive().max(500).optional().default(50),operations:W.array(W.string().min(1)).optional()});function on(a){if(!a)return{};try{return JSON.parse(a)}catch{return a}}function sn(a){return{auditId:a.auditid,operation:a.operation,operationName:pe[a.operation]??`Unknown(${a.operation})`,action:a.action,actionName:pe[a.action]??`Unknown(${a.action})`,createdOn:a.createdon,userId:a._userid_value,userFullName:a.userid?.fullname??"",userDomainName:a.userid?.domainname??"",objectId:a._objectid_value,objectTypeCode:a.objecttypecode,changes:on(a.changedata)}}async function rt(a,i,r){switch(a){case"dataverse_get_audit_log":{let e=rn.parse(i),t=[];if(e.recordId&&t.push(`_objectid_value eq ${e.recordId}`),e.entityLogicalName){let n=v(e.entityLogicalName);t.push(`objecttypecode eq '${n}'`)}if(e.userId&&t.push(`_userid_value eq ${e.userId}`),e.fromDate&&t.push(`createdon ge ${e.fromDate}`),e.operations?.length){let n=e.operations.map(o=>nn[o]).filter(o=>o!==void 0);if(n.length>0){let o=n.map(l=>`action eq ${l}`).join(" or ");t.push(`(${o})`)}}try{let o=(await r.query("audits",{select:["auditid","action","operation","createdon","_objectid_value","objecttypecode","changedata","_userid_value"],...t.length>0?{filter:t.join(" and ")}:{},orderby:"createdon desc",top:e.top,expand:"userid($select=fullname,domainname)"})).value.map(sn);return u(`${o.length} audit records for ${e.entityLogicalName??e.recordId??"query"}`,{entries:o,count:o.length},["Filter by operation type for specific changes"])}catch(n){let o=n instanceof Error?n.message:String(n);if(o.includes("403")||o.includes("Forbidden"))return{content:[{type:"text",text:JSON.stringify({isError:!0,error:"Audit log access denied (HTTP 403). Ensure auditing is enabled on the Dataverse environment and the target table, and that the authenticated user has sufficient privileges."})}]};throw n}}default:throw new Error(`Unknown audit tool: ${a}`)}}import{z as Q}from"zod";var it=new Map,ot=[{name:"dataverse_detect_duplicates",description:"Checks for potential duplicate records before creating, using a FetchXML query that matches records sharing the same field values as the prospective record (OR conditions on the provided fields). Pass the prospective record fields to check against existing records. WHEN TO USE: Before creating a new record to check if a similar record already exists. BEST PRACTICES: Pass only the key identifying fields (e.g., name, email); too many fields will return fewer matches. WORKFLOW: create_record.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:'Table to check, e.g., "account"'},record:{type:"object",description:"The prospective record fields to check for duplicates"},top:{type:"number",description:"Maximum number of duplicates to return (default 5, max 20)"}},required:["entityLogicalName","record"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ln=Q.object({entityLogicalName:Q.string().min(1).regex(/^[a-z_][a-z0-9_]*$/i,"Must be a valid Dataverse logical name"),record:Q.record(Q.string(),Q.unknown()),top:Q.number().int().positive().max(20).optional().default(5)});async function st(a,i,r){switch(a){case"dataverse_detect_duplicates":{let n=function(m){return String(m).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")};var e=n;let t=ln.parse(i),o=Object.entries(t.record).filter(([,m])=>m!=null).map(([m,f])=>`<condition attribute="${n(m)}" operator="eq" value="${n(String(f))}" />`).join(`
|
|
2
|
+
import{a as he}from"./chunk-OQ46VPYS.js";import{a as be}from"./chunk-M5UROAVJ.js";import"./chunk-RYRO3QPE.js";import"./chunk-KJ3HM2VM.js";import{a as v,b as _e}from"./chunk-FSM3J3WD.js";import{Server as Wn}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as jn}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fn,ListToolsRequestSchema as Bn,ListResourcesRequestSchema as Gn,ListResourceTemplatesRequestSchema as zn,ReadResourceRequestSchema as Vn}from"@modelcontextprotocol/sdk/types.js";import{readFileSync as Kn}from"fs";import{join as Xn,dirname as Qn}from"path";import{fileURLToPath as Yn}from"url";function we(){let a=process.argv.slice(2),i="stdio",r=3e3;for(let e=0;e<a.length;e++){if(a[e]==="--transport"&&a[e+1]){let t=a[e+1];(t==="http"||t==="stdio")&&(i=t),e++}if(a[e]==="--port"&&a[e+1]){let t=parseInt(a[e+1],10);!isNaN(t)&&t>0&&t<65536&&(r=t),e++}}return{transport:i,port:r}}var se=class{map=new Map;register(i){for(let r of i.tools){if(this.map.has(r.name))throw new Error(`Duplicate tool name: ${r.name}`);this.map.set(r.name,{definition:r,handler:i.handler})}}getHandler(i){return this.map.get(i)?.handler}getAllDefinitions(){return Array.from(this.map.values()).map(i=>i.definition)}has(i){return this.map.has(i)}get size(){return this.map.size}};function Se(a){let i=new se;for(let r of a)i.register(r);return i}var J=class{server;token;constructor(i,r){this.server=i??null,this.token=r??null}async report(i,r){if(!(!this.server||this.token==null))try{await this.server.notification({method:"notifications/progress",params:{progressToken:this.token,progress:i,total:r}})}catch{}}},rr=new J;function O(a){let i=a.errorCategory!==void 0,r=i?{...a,isError:!0,content:[{type:"text",text:a.summary}]}:a,e={content:[{type:"text",text:JSON.stringify(r,null,2)}]};return i&&(e.isError=!0),e}function u(a,i,r,e){return O({summary:a,data:i,suggestions:r,warnings:e})}function H(a,i,r){return O({summary:`${i.length} ${a} found.`,data:i,suggestions:r})}function te(a,i="UNKNOWN",r){return O({summary:a,data:null,errorCategory:i,suggestions:r})}function X(a){let i={feature_disabled:"ENV_LIMITATION",permission_required:"PERMISSIONS",schema_missing:"SCHEMA_MISMATCH"};return O({summary:`Blocked: ${a.cannotProceedBecause}`,data:null,prerequisite:a,errorCategory:i[a.type]})}var le="@OData.Community.Display.V1.FormattedValue";function G(a){return a.map(i=>{if(typeof i!="object"||i===null)return i;let r=i,e={};for(let n of Object.keys(r))if(n.endsWith(le)){let o=n.slice(0,-le.length);e[o]=r[n]}if(Object.keys(e).length===0)return i;let t={};for(let n of Object.keys(r))n.endsWith(le)||(Object.prototype.hasOwnProperty.call(e,n)?t[n]={value:r[n],label:e[n]}:t[n]=r[n]);return t})}var Te=[{name:"dataverse_whoami",description:"Returns the current authenticated user context from Dataverse WhoAmI: userId, businessUnitId, organizationId, organizationName, and environmentUrl. Use this to verify authentication is working, retrieve the current user context, or obtain IDs needed for subsequent operations. WHEN TO USE: Verifying authentication, getting current user/org context, or obtaining IDs for downstream operations. BEST PRACTICES: Call first to confirm connectivity before other tools. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}];async function Ne(a,i,r){if(a==="dataverse_whoami"){let e=await r.whoAmI(),t={userId:e.UserId,businessUnitId:e.BusinessUnitId,organizationId:e.OrganizationId,organizationName:e.OrganizationName,environmentUrl:e.EnvironmentUrl};return u(`Authenticated as ${e.OrganizationName}`,t,["Use dataverse_list_tables to discover available tables","Use dataverse_get_table_metadata to inspect a table schema"])}throw new Error(`Unknown auth tool: ${a}`)}import{z as ke}from"zod";import{z as R}from"zod";var de=[{name:"dataverse_list_tables",description:"Lists all Dataverse tables. By default returns ONLY custom (non-system) tables. Set includeSystemTables=true to include all ~1700+ system tables. WHEN TO USE: Discovering which tables exist in the environment. BEST PRACTICES: Start without includeSystemTables to focus on custom tables; use dataverse_get_table_metadata for column details. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{includeSystemTables:{type:"boolean",description:"Include system tables. Default false = custom tables only."}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_table_metadata",description:"Returns full schema metadata for a Dataverse table: all attribute (column) logical names, display names, data types, required levels, and lookup target entities. Use this before writing queries or creating/updating records to confirm correct field names and types. Set includeAttributes=false if you only need table-level metadata without column details. WHEN TO USE: Before building queries or creating/updating records to validate field names, types, and required levels. BEST PRACTICES: Set includeAttributes=false for table-level info only; call once and reuse results. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{logicalName:{type:"string",description:'The logical name of the table (e.g., "account", "contact", "new_mytable")'},includeAttributes:{type:"boolean",description:"Include attribute (column) definitions. Default: true"}},required:["logicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_relationships",description:"Returns all relationship definitions (1:N, N:1, N:N) for a Dataverse table, including relationship schema names, referenced/referencing entity names, and lookup attribute names. Use to determine the correct relationshipName for dataverse_associate/dataverse_disassociate, or to map lookup fields before building FetchXML joins. Use relationshipType to filter results. WHEN TO USE: Finding relationship schema names for associate/disassociate, or mapping lookups for FetchXML joins. BEST PRACTICES: Filter by relationshipType to reduce output size. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{logicalName:{type:"string",description:"The logical name of the table"},relationshipType:{type:"string",enum:["OneToMany","ManyToOne","ManyToMany","All"],description:"Filter by relationship type. Default: All"}},required:["logicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_global_option_sets",description:"Lists all global (shared) option sets defined in the Dataverse environment, returning their names and metadata IDs. Use this to discover available option sets before calling dataverse_get_option_set to retrieve their values. Prefer this over dataverse_get_table_metadata when you need to find option sets that are reused across multiple tables. WHEN TO USE: Discovering shared option sets reused across multiple tables. BEST PRACTICES: Follow up with dataverse_get_option_set to retrieve individual option values. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_option_set",description:"Returns all option labels and their integer values for a named global option set. Use this to look up the numeric code for a picklist value before filtering records (e.g., statecode or statuscode equivalents), or to populate dropdowns with correct option values. WHEN TO USE: Looking up numeric codes for picklist filtering or record creation. BEST PRACTICES: Use the integer values in $filter expressions, not the label text. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The name of the global option set"}},required:["name"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_entity_key",description:"Returns all alternate key definitions for a Dataverse table. Useful before using dataverse_upsert to know which fields serve as alternate keys, their index status (Active/InProgress/Failed), and whether they are customizable. WHEN TO USE: Before using dataverse_upsert to verify which fields serve as alternate keys. BEST PRACTICES: Check that key index status is Active before relying on a key. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{tableName:{type:"string",description:'Logical name of the table (e.g., "account", "contact")'}},required:["tableName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_attribute_option_set",description:"Returns all option labels and integer values for a table-specific attribute (Picklist, MultiSelectPicklist, Status, or State field). Use to look up the numeric codes for a column's choices before filtering or updating records. WHEN TO USE: Looking up option values for a specific table's picklist, multi-select picklist, status, or state column. BEST PRACTICES: Use the integer values in $filter and when setting fields in create/update. WORKFLOW: explore_schema.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:'Logical name of the table (e.g., "account", "contact")'},attributeLogicalName:{type:"string",description:'Logical name of the attribute (e.g., "statuscode", "industrycode", "new_tags")'}},required:["entityLogicalName","attributeLogicalName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Yt=R.object({logicalName:R.string().min(1),includeAttributes:R.boolean().optional().default(!0)}),Jt=R.object({logicalName:R.string().min(1),relationshipType:R.enum(["OneToMany","ManyToOne","ManyToMany","All"]).optional()}),Zt=R.object({includeSystemTables:R.boolean().default(!1).optional().describe("Include system tables. Default false = custom tables only.")}),ea=R.object({name:R.string().min(1)}),ta=R.object({tableName:R.string().min(1).describe('Logical name of the table (e.g., "account", "contact")')}),Re=/^[a-z_][a-z0-9_]*$/,aa=R.object({entityLogicalName:R.string().min(1).regex(Re,"Invalid Dataverse logical name"),attributeLogicalName:R.string().min(1).regex(Re,"Invalid Dataverse logical name")});async function Ae(a,i,r){switch(a){case"dataverse_list_tables":{let{includeSystemTables:e=!1}=Zt.parse(i??{}),t=!e,n=await r.listTables(t),o=Array.isArray(n)?n:[];return H(`tables (${t?"custom only":"including system"})`,o,["Use dataverse_get_table_metadata to inspect a specific table's columns and types"])}case"dataverse_get_table_metadata":{let{logicalName:e,includeAttributes:t}=Yt.parse(i),n=await r.getTableMetadata(e,t),o=Array.isArray(n?.Attributes)?n.Attributes.length:0;return u(`Metadata for ${e}: ${o} attributes`,n,["Use dataverse_query to read records from this table","Use dataverse_get_relationships to see related tables"])}case"dataverse_get_relationships":{let{logicalName:e,relationshipType:t}=Jt.parse(i),n=await r.getRelationships(e),o=e.toLowerCase(),l=n.filter(m=>m.RelationshipType==="OneToManyRelationship"&&m.ReferencedEntity?.toLowerCase()===o),s=n.filter(m=>m.RelationshipType==="OneToManyRelationship"&&m.ReferencingEntity?.toLowerCase()===o),d=n.filter(m=>m.RelationshipType==="ManyToManyRelationship"),c=!t||t==="All",p=[...c||t==="OneToMany"?l:[],...c||t==="ManyToOne"?s:[],...c||t==="ManyToMany"?d:[]];return u(`${p.length} relationships found for ${e}`,{tableName:e,oneToMany:c||t==="OneToMany"?l:void 0,manyToOne:c||t==="ManyToOne"?s:void 0,manyToMany:c||t==="ManyToMany"?d:void 0},["Use dataverse_associate or dataverse_disassociate to manage N:N relationships"])}case"dataverse_list_global_option_sets":{let e=await r.listGlobalOptionSets(),t=Array.isArray(e)?e:[];return H("global option sets",t,["Use dataverse_get_option_set with a name to see the values"])}case"dataverse_get_option_set":{let{name:e}=ea.parse(i),t=await r.getOptionSet(e),n=Array.isArray(t?.Options)?t.Options:[];return u(`Option set '${e}': ${n.length} options`,t,["Use these values in $filter expressions or when creating/updating records"])}case"dataverse_get_entity_key":{let{tableName:e}=ta.parse(i),t=await r.getEntityKeys(e);return u(`${t.length} alternate keys for ${e}`,{tableName:e,keys:t,count:t.length},["Use dataverse_upsert with these keys for create-or-update operations"])}case"dataverse_get_attribute_option_set":{let{entityLogicalName:e,attributeLogicalName:t}=aa.parse(i),n=await r.getAttributeOptionSet(e,t),o=Array.isArray(n?.options)?n.options:[];return u(`Attribute '${t}' on '${e}': ${o.length} options`,n,["Use these integer values in $filter or when creating/updating records"])}default:throw new Error(`Unknown metadata-read tool: ${a}`)}}import{z}from"zod";var na=new Set(["dataverse_delete","dataverse_delete_file","dataverse_update_entity","dataverse_delete_attribute"]);function ce(a){let i=[];return a.top!==void 0&&a.top>5e3&&i.push({code:"LARGE_RESULT_SET",message:`Large result set requested (top=${a.top}). Consider reducing $top or using paging.`,severity:"warning"}),(!a.select||a.select.length===0)&&i.push({code:"NO_SELECT",message:"No $select specified \u2014 all columns will be returned. Specify columns to reduce payload size.",severity:"warning"}),a.filter||i.push({code:"NO_FILTER",message:"No filter \u2014 consider adding one to reduce results and improve performance.",severity:"info"}),i}function q(a){let i=[];return na.has(a.toolName)&&i.push({code:"DESTRUCTIVE_OP",message:"Destructive operation \u2014 confirm with user before proceeding. This action cannot be undone.",severity:"warning"}),a.toolName==="dataverse_batch_execute"&&i.push({code:"BATCH_OP",message:"Batch operations may modify multiple records in a single transaction.",severity:"info"}),i}var Ee=[{name:"dataverse_update_entity",description:"Updates configuration flags on an existing Dataverse entity definition \u2014 enables or disables Notes (HasNotes), Change Tracking, and Audit. Requires System Customizer or System Administrator privileges. WHEN TO USE: Enabling notes/attachments support, change tracking, or audit on a table. BEST PRACTICES: Leave autoPublish=false (default) when making multiple changes and call dataverse_publish_customizations once at the end. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:"Logical name of the entity to update (e.g. 'account', 'new_mytable')"},hasNotes:{type:"boolean",description:"Enable or disable the Notes/Attachments feature for this entity"},changeTrackingEnabled:{type:"boolean",description:"Enable or disable change tracking (required for delta sync)"},isAuditEnabled:{type:"boolean",description:"Enable or disable auditing on this entity"},autoPublish:{type:"boolean",description:"Publish the customization after update (default: false). For batch operations leave false and publish once at the end."},confirm:{type:"boolean",description:"Must be true \u2014 confirms intentional schema modification to Dataverse entity metadata"}},required:["entityLogicalName","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ra=z.object({entityLogicalName:z.string().min(1).regex(/^[a-z_][a-z0-9_]*$/,"Invalid logical name"),confirm:z.literal(!0,{errorMap:()=>({message:"Set confirm: true to modify entity metadata"})}),hasNotes:z.boolean().optional(),changeTrackingEnabled:z.boolean().optional(),isAuditEnabled:z.boolean().optional(),autoPublish:z.boolean().optional().default(!1)}).refine(a=>a.hasNotes!==void 0||a.changeTrackingEnabled!==void 0||a.isAuditEnabled!==void 0,{message:"At least one of hasNotes, changeTrackingEnabled, or isAuditEnabled must be provided"});async function xe(a,i,r){switch(a){case"dataverse_update_entity":{let{entityLogicalName:e,hasNotes:t,changeTrackingEnabled:n,isAuditEnabled:o,autoPublish:l}=ra.parse(i),s=q({toolName:"dataverse_update_entity",entitySetName:e}).map(f=>`[${f.severity.toUpperCase()}] ${f.code}: ${f.message}`),d={};t!==void 0&&(d.HasNotes=t),n!==void 0&&(d.ChangeTrackingEnabled=n),o!==void 0&&(d.IsAuditEnabled=o);try{await r.getTableMetadata(e,!1)}catch{return O({summary:`Entity '${e}' does not exist in this Dataverse environment.`,data:{error:"ENTITY_NOT_FOUND",entityLogicalName:e},errorCategory:"SCHEMA_MISMATCH",suggestions:["Use dataverse_list_tables to find the correct entity logical name"]})}let c={"@odata.type":"#Microsoft.Dynamics.CRM.EntityMetadata"};t!==void 0&&(c.HasNotes=t),n!==void 0&&(c.ChangeTrackingEnabled=n),o!==void 0&&(c.IsAuditEnabled={Value:o});try{await r.updateEntityDefinition(e,c)}catch(f){if((f instanceof Error?f.message:String(f)).includes("0x80060888")){let K=Object.keys(d),F={IsAuditEnabled:"IsAuditEnabled requires organization-level auditing to be enabled first: Power Platform admin center > Settings > Audit and logs > Start Auditing",ChangeTrackingEnabled:"ChangeTrackingEnabled may be blocked if the entity is part of a managed solution",HasNotes:"HasNotes=true/false should work for custom (unmanaged) entities"},Y=K.map(B=>F[B]).filter(B=>B!==void 0);return O({summary:`Cannot update entity '${e}': operation not supported by Dataverse.`,data:{error:"0x80060888",entityLogicalName:e,requestedChanges:d},errorCategory:"SCHEMA_MISMATCH",suggestions:Y})}throw f}let p=Object.entries(d).map(([f,N])=>`${f}=${String(N)}`).join(", "),m="autoPublish=false (skipped)";return l&&(await r.publishCustomizations({entities:[e]}),m="published successfully"),u(`Entity '${e}' updated: ${p}. ${m}.`,{entityLogicalName:e,changes:d,published:l,...s.length>0&&{warnings:s}},["Use dataverse_get_table_metadata to verify the changes","Enable HasNotes=true before using dataverse_create_annotation"])}default:throw new Error(`Unknown metadata tool: ${a}`)}}var ia={name:"dataverse_resolve_entity_name",description:"Resolves entity names bidirectionally: input a logicalName (e.g. 'account') or entitySetName (e.g. 'accounts') and get both representations plus metadata. Essential for avoiding 404/0x80060888 errors caused by using logicalName in OData URLs (which require entitySetName). WHEN TO USE: Any time you're unsure whether to use logicalName vs entitySetName, or to look up PrimaryIdAttribute/PrimaryNameAttribute for new entities. EXAMPLES: 'account' \u2192 LogicalName:account, EntitySetName:accounts, PrimaryId:accountid. WORKFLOW: read_record, inspect_audit.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Entity name to resolve \u2014 either logicalName (e.g. 'account') or entitySetName (e.g. 'accounts')"}},required:["name"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},oa=ke.object({name:ke.string().min(1)}),Ie=[...de,ia,...Ee];async function De(a,i,r){if(a==="dataverse_resolve_entity_name"){let{name:t}=oa.parse(i),n=`LogicalName eq '${t}' or EntitySetName eq '${t}'`,l=(await r.query("EntityDefinitions",{filter:n,select:["LogicalName","EntitySetName","DisplayName","PrimaryIdAttribute","PrimaryNameAttribute","OwnershipType","IsCustomEntity"]})).value??[];if(l.length===0)return u(`No entity found matching '${t}'`,{name:t,matches:[]},["Try dataverse_list_tables to see available table names"]);let s=l[0],d=s.DisplayName?.UserLocalizedLabel?.Label??null;return u(`Resolved '${t}' \u2192 LogicalName: ${s.LogicalName}, EntitySetName: ${s.EntitySetName}`,{logicalName:s.LogicalName,entitySetName:s.EntitySetName,displayName:d,primaryIdAttribute:s.PrimaryIdAttribute,primaryNameAttribute:s.PrimaryNameAttribute,ownershipType:s.OwnershipType,isCustomEntity:s.IsCustomEntity},["Use entitySetName in OData Web API URLs (dataverse_query, dataverse_read_record)","Use logicalName in FetchXML and metadata API calls"])}return new Set(de.map(t=>t.name)).has(a)?Ae(a,i,r):xe(a,i,r)}import{z as _}from"zod";import{z as Oe}from"zod";var Ce=/^[a-zA-Z_][a-zA-Z0-9_]*$/,h=Oe.string().min(1).regex(Ce,"entitySetName must contain only letters, digits, or underscores"),ae=Oe.string().min(1).regex(Ce,"relationshipName must contain only letters, digits, or underscores");var sa={opportunity:"opportunities",territory:"territories",category:"categories",activityparty:"activityparties",activitymimeattachment:"activitymimeattachments",queue:"queues",queueitem:"queueitems",dependency:"dependencies",salesliteratureitem:"salesliteratureitems",contractdetail:"contractdetails",discounttype:"discounttypes",entitlementtemplate:"entitlementtemplates",pricelevel:"pricelevels"},Ue=[{name:"dataverse_query",description:"Queries a Dataverse table using OData ($filter, $select, $orderby, $top, $expand, $count). Use for simple to moderate reads on a single table or with shallow $expand for related fields. Always specify $select to minimize payload. For complex aggregations (count, sum, avg), multi-entity joins, many-to-many traversal, or advanced FetchXML-only operators, use dataverse_execute_fetchxml instead. WHEN TO USE: Single-table reads, simple filters, shallow expands, or server-side aggregation via $apply. BEST PRACTICES: Always pass $select; cap with $top; use $apply for server-side counts/grouping. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'The OData entity set name (e.g., "accounts", "contacts", "new_mytables")'},select:{type:"array",items:{type:"string"},description:"Columns to return. Always specify to minimize payload."},filter:{type:"string",description:'OData $filter expression (e.g., "statecode eq 0 and new_amount gt 1000")'},orderby:{type:"string",description:'OData $orderby expression (e.g., "createdon desc")'},top:{type:"number",description:"Maximum number of records to return (default: 50)"},expand:{type:"string",description:'OData $expand for related entities (e.g., "parentaccountid($select=name)")'},count:{type:"boolean",description:"Include total record count in response"},apply:{type:"string",description:'OData $apply for server-side aggregation (e.g., "groupby((statuscode),aggregate($count as count))")'},formattedValues:{type:"boolean",description:"When true, includes human-readable labels for picklist fields alongside raw integer codes (e.g., { value: 1, label: 'Active' }). Uses OData formatted-value annotations."}},required:["entitySetName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_fetchxml",description:'Executes a FetchXML query against Dataverse \u2014 use for complex scenarios requiring aggregations (count, sum, avg, min, max with grouping), linked-entity joins across multiple tables, many-to-many relationship traversal, or advanced filtering not expressible in OData. Returns a typed array of records. entitySetName is optional \u2014 if omitted it is extracted from the <entity name="..."> element in the FetchXML. Set fetchAll=true to automatically paginate through all pages (up to 100 pages / 50,000 records for safety). Default behavior returns first page only. WHEN TO USE: Multi-table joins, aggregations with groupby, N:N traversal, or filtering not supported by OData. BEST PRACTICES: Add page/count attributes for large result sets; prefer dataverse_query for simple reads; use fetchAll=true to retrieve all records beyond the first page. WORKFLOW: query_data.',inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name of the root entity (e.g., "accounts"). If omitted, extracted from the <entity name="..."> element in the FetchXML.'},fetchXml:{type:"string",description:"The complete FetchXML query string"},fetchAll:{type:"boolean",description:"When true, automatically paginates through all result pages (up to 100 pages / 50,000 records). Default false returns the first page only."},formattedValues:{type:"boolean",description:"When true, includes human-readable labels for picklist fields alongside raw integer codes."}},required:["fetchXml"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_retrieve_multiple_with_paging",description:"Retrieves ALL records matching a query by automatically following OData nextLink pages. Use instead of dataverse_query when you need more than 5000 records or all records in a table. Returns totalRetrieved count. Set maxTotal to cap retrieval (default 5000, max 50000) to avoid overwhelming the context. WHEN TO USE: You need all matching records beyond the 5000-row OData page limit or a full table export. BEST PRACTICES: Always set $select; use maxTotal to cap results and avoid context overflow. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name (e.g., "accounts")'},select:{type:"array",items:{type:"string"}},filter:{type:"string"},orderby:{type:"string"},expand:{type:"string"},maxTotal:{type:"number",description:"Maximum records to retrieve (default: 5000, max: 50000)"},formattedValues:{type:"boolean",description:"Include formatted label annotations."}},required:["entitySetName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],la=_.object({entitySetName:h,select:_.array(_.string()).optional(),filter:_.string().optional(),orderby:_.string().optional(),top:_.number().positive().max(5e3).optional().default(50),expand:_.string().optional(),count:_.boolean().optional(),apply:_.string().optional(),formattedValues:_.boolean().optional()}),da=_.object({fetchXml:_.string().min(1).describe("Complete FetchXML query string"),entitySetName:_.string().optional().describe('OData entity set name (e.g., "accounts"). If omitted, extracted from the <entity name="..."> element in the FetchXML.'),fetchAll:_.boolean().optional(),formattedValues:_.boolean().optional()}),ca=_.object({entitySetName:h,select:_.array(_.string()).optional(),filter:_.string().optional(),orderby:_.string().optional(),expand:_.string().optional(),maxTotal:_.number().positive().max(5e4).optional(),formattedValues:_.boolean().optional()});async function Le(a,i,r,e){switch(a){case"dataverse_query":{let t=la.parse(i),n={};t.select!==void 0&&(n.select=t.select),t.filter!==void 0&&(n.filter=t.filter),t.orderby!==void 0&&(n.orderby=t.orderby),t.top!==void 0&&(n.top=t.top),t.expand!==void 0&&(n.expand=t.expand),t.count!==void 0&&(n.count=t.count),t.apply!==void 0&&(n.apply=t.apply),t.formattedValues!==void 0&&(n.formattedValues=t.formattedValues);let o;try{o=await r.query(t.entitySetName,n)}catch(f){let N=f,F=N?.data?.error?.message,Y=f instanceof Error?f.message:String(f),B=N?.status,Qt=B===403||B===401?"PERMISSIONS":B===404?"SCHEMA_MISMATCH":"UNKNOWN";return te(`dataverse_query failed on '${t.entitySetName}': ${F??Y}`,Qt,["Use dataverse_list_tables to verify the entity set name","Check OData filter syntax and column names"])}let l=Array.isArray(o?.value)?o.value:[],s=o["@odata.count"],d=s!==void 0?s===l.length?" (showing all results)":` (total in dataset: ${s})`:"",c=t.formattedValues?G(l):l,p=t.formattedValues&&Array.isArray(o?.value)?{...o,value:c}:o,m=ce({...t.top!==void 0?{top:t.top}:{},...t.select!==void 0?{select:t.select}:{},...t.filter!==void 0?{filter:t.filter}:{},entitySetName:t.entitySetName}).map(f=>`[${f.severity.toUpperCase()}] ${f.code}: ${f.message}`);return u(`${l.length} records returned from ${t.entitySetName}${d}`,p,["Use dataverse_execute_fetchxml for complex joins or aggregations","Add $select to minimize payload"],m.length>0?m:void 0)}case"dataverse_execute_fetchxml":{let t=da.parse(i),n=t.entitySetName,{fetchXml:o}=t;if(!n){let c=o.match(/<entity\s+name=["']([^"']+)["']/i);if(!c)return{content:[{type:"text",text:JSON.stringify({isError:!0,error:"entitySetName is required when not present in FetchXML <entity> element"})}]};let p=c[1];n=sa[p]??p+"s"}if(t.fetchAll){let c=await r.executeFetchXmlAllPages(n,o),p=t.formattedValues?G(c):c;return u(`${p.length} records retrieved via FetchXML (all pages)`,p,["Use dataverse_query for simple OData reads","fetchAll paginated through all available pages (up to 100)"])}let l=await r.executeFetchXml(n,o,t.formattedValues),s=Array.isArray(l)?l:Array.isArray(l?.value)?l.value:[],d=t.formattedValues?G(s):s;return u(`${d.length} records returned via FetchXML`,t.formattedValues&&Array.isArray(l?.value)?{...l,value:d}:l,["Use dataverse_query for simple OData reads","Add page/count attributes for large result sets"])}case"dataverse_retrieve_multiple_with_paging":{let t=ca.parse(i),n={};t.select!==void 0&&(n.select=t.select),t.filter!==void 0&&(n.filter=t.filter),t.orderby!==void 0&&(n.orderby=t.orderby),t.expand!==void 0&&(n.expand=t.expand),t.maxTotal!==void 0&&(n.maxTotal=t.maxTotal),await e?.report(0,1);let o=await r.queryWithPaging(t.entitySetName,n),l=o?.totalRetrieved??(Array.isArray(o?.value)?o.value.length:0),s=o?.pageCount??1;await e?.report(1,1);let d=Array.isArray(o?.records)?o.records:[],c=t.formattedValues&&d.length>0?{...o,records:G(d)}:o,p=ce({...t.select!==void 0?{select:t.select}:{},...t.filter!==void 0?{filter:t.filter}:{},entitySetName:t.entitySetName}).map(m=>`[${m.severity.toUpperCase()}] ${m.code}: ${m.message}`);return u(`${l} records retrieved across ${s} pages from ${t.entitySetName}`,c,["Set maxTotal to limit retrieval","Use $select to minimize payload size"],p.length>0?p:void 0)}default:throw new Error(`Unknown query tool: ${a}`)}}import{z as y}from"zod";var Pe=[{name:"dataverse_get",description:"Retrieves a single Dataverse record by its GUID. Use when you already know the exact record ID and want specific fields \u2014 faster and more precise than dataverse_query with a GUID filter. Specify select to limit returned columns and reduce payload size. WHEN TO USE: You have the exact record GUID and want specific fields. BEST PRACTICES: Always specify select; use the returned etag for subsequent optimistic-concurrency updates. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name"},id:{type:"string",description:"Record GUID"},select:{type:"array",items:{type:"string"},description:"Columns to return"},expand:{type:"string",description:'OData $expand for related entities (e.g., "parentaccountid($select=name)")'},formattedValues:{type:"boolean",description:"When true, annotates picklist fields with human-readable labels. Note: dataverse_get always requests all OData annotations."}},required:["entitySetName","id"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_create",description:`Creates a new record in a Dataverse table and returns the new record's GUID. Use dataverse_get_table_metadata first to confirm correct logical field names and required fields. For setting lookup fields, use the format "_fieldname_value" with the related record GUID. For bulk creation of multiple records, consider dataverse_batch_execute to reduce HTTP round-trips. WHEN TO USE: Creating a single new record in a known table. BEST PRACTICES: Validate field names via dataverse_get_table_metadata first; use dataverse_batch_execute for bulk inserts. WORKFLOW: create_record.`,inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name"},data:{type:"object",description:"Record data as key-value pairs using logical names"}},required:["entitySetName","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_update",description:"Updates an existing Dataverse record using PATCH semantics \u2014 only the fields provided in data are changed, all other fields remain unchanged. Requires the record GUID. Use dataverse_upsert instead if you want to create-or-update using an alternate key without knowing the GUID upfront. WHEN TO USE: Modifying specific fields on an existing record with a known GUID. BEST PRACTICES: Pass etag for optimistic concurrency; include only changed fields in data. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},data:{type:"object",description:"Fields to update"},etag:{type:"string",description:"ETag value from a prior dataverse_get response. When provided, the update only succeeds if the record has not been modified since (optimistic concurrency). Prevents lost updates."}},required:["entitySetName","id","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_delete",description:"Permanently deletes a Dataverse record by its GUID. This operation is irreversible \u2014 you MUST set confirm=true to proceed. Use dataverse_list_dependencies to check if the record is referenced by workflows, plugins, or other components before deleting shared or configuration records. WHEN TO USE: Permanently removing a record you are certain should be deleted. BEST PRACTICES: Call dataverse_list_dependencies first for shared records; always set confirm=true. WORKFLOW: delete_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},confirm:{type:"boolean",description:"Must be explicitly true to proceed with deletion"}},required:["entitySetName","id","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_upsert",description:'Creates or updates a Dataverse record using an alternate key (no GUID needed). Returns operation="created" or "updated". Use mode="createOnly" to fail with an error if the record already exists, or mode="updateOnly" to fail if the record does not exist. Default mode="upsert" creates or updates. Supports compound alternate keys via the alternateKeys parameter. WHEN TO USE: Create-or-update when you have an alternate key but no GUID. BEST PRACTICES: Verify alternate keys with dataverse_get_entity_key first; use mode to control behavior. WORKFLOW: create_record.',inputSchema:{type:"object",properties:{entitySetName:{type:"string"},alternateKey:{type:"string",description:"Alternate key attribute name (for single key)"},alternateKeyValue:{type:"string",description:"Alternate key value (for single key)"},alternateKeys:{type:"object",description:'Compound alternate key as key-value map (e.g., {"key1":"val1","key2":"val2"}). Use instead of alternateKey/alternateKeyValue for multi-field keys.'},data:{type:"object",description:"Record data"},mode:{type:"string",enum:["upsert","createOnly","updateOnly"],description:"upsert=create or update (default), createOnly=fail if exists, updateOnly=fail if not found"}},required:["entitySetName","data"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_assign",description:"Assigns a Dataverse record to a different user or team owner. Sets the ownerid lookup field using the OData bind syntax. WHEN TO USE: Changing the owner of a record to a different user or team. BEST PRACTICES: Use dataverse_list_users or dataverse_list_teams to find valid owner GUIDs first. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"OData entity set name of the record to assign"},id:{type:"string",description:"Record GUID"},ownerType:{type:"string",enum:["systemuser","team"],description:'Type of the new owner: "systemuser" for a user, "team" for a team'},ownerId:{type:"string",description:"GUID of the user or team to assign the record to"}},required:["entitySetName","id","ownerType","ownerId"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ua=y.object({entitySetName:h,id:y.string().uuid(),select:y.array(y.string()).optional(),expand:y.string().optional(),formattedValues:y.boolean().optional()}),pa=y.object({entitySetName:h,data:y.record(y.unknown())}),ma=y.object({entitySetName:h,id:y.string().uuid(),data:y.record(y.unknown()),etag:y.string().optional()}),ga=y.object({entitySetName:h,id:y.string().uuid(),confirm:y.boolean()}),fa=y.object({entitySetName:h,alternateKey:y.string().min(1).optional(),alternateKeyValue:y.string().min(1).optional(),alternateKeys:y.record(y.string()).optional(),data:y.record(y.unknown()),mode:y.enum(["upsert","createOnly","updateOnly"]).default("upsert").optional().describe("upsert=create or update (default), createOnly=fail if exists, updateOnly=fail if not found")}).refine(a=>a.alternateKey&&a.alternateKeyValue||a.alternateKeys&&Object.keys(a.alternateKeys).length>0,{message:"Provide either (alternateKey + alternateKeyValue) or alternateKeys"}),ya=y.object({entitySetName:h,id:y.string().uuid(),ownerType:y.enum(["systemuser","team"]),ownerId:y.string().uuid()});async function qe(a,i,r){switch(a){case"dataverse_get":{let{entitySetName:e,id:t,select:n,expand:o,formattedValues:l}=ua.parse(i),s,d;try{let p=await r.getRecord(e,t,n,o);s=p.record,d=p.etag}catch(p){let m=p,N=m?.data?.error?.message,K=p instanceof Error?p.message:String(p),F=m?.status,Y=F===403||F===401?"PERMISSIONS":F===404?"SCHEMA_MISMATCH":"UNKNOWN";return te(`dataverse_get failed for '${t}' in '${e}': ${N??K}`,Y,["Use dataverse_list_tables to verify the entity set name","Verify the record ID exists with dataverse_query"])}let c=l?G([s])[0]??s:s;return u(`Retrieved record ${t} from ${e}`,{id:t,record:c,etag:d},["Use dataverse_update to modify this record","Use dataverse_get_relationships to explore related records"])}case"dataverse_create":{let{entitySetName:e,data:t}=pa.parse(i),n=await r.createRecord(e,t);return u(`Created record ${n} in ${e}`,{id:n,message:"Record created successfully"},["Use dataverse_get to retrieve the full record","Use dataverse_associate to link related records"])}case"dataverse_update":{let{entitySetName:e,id:t,data:n,etag:o}=ma.parse(i);return await r.updateRecord(e,t,n,o),u(`Updated record ${t} in ${e}`,{message:"Record updated successfully"},["Use dataverse_get to verify the update","Use etag parameter for optimistic concurrency"])}case"dataverse_delete":{let{entitySetName:e,id:t,confirm:n}=ga.parse(i);return n?(await r.deleteRecord(e,t),u(`Deleted record ${t} from ${e}`,{message:"Record deleted successfully"},["This operation is irreversible"])):{content:[{type:"text",text:JSON.stringify({message:`Deletion not performed. Set 'confirm: true' to delete record '${t}' from '${e}'.`})}]}}case"dataverse_upsert":{let e=fa.parse(i),{entitySetName:t,alternateKey:n,alternateKeyValue:o,alternateKeys:l,data:s,mode:d="upsert"}=e,c;l&&Object.keys(l).length>0&&(c=Object.entries(l).map(([m,f])=>`${v(m)}='${v(f)}'`).join(","));let p=await r.upsertRecord(t,n??"",o??"",s,d,c);return u(`Upsert ${p.operation}: record in ${t}`,{operation:p.operation,id:p.id,message:`Record ${p.operation} successfully`},["Use dataverse_get_entity_key to verify alternate key definitions"])}case"dataverse_assign":{let{entitySetName:e,id:t,ownerType:n,ownerId:o}=ya.parse(i),l=n==="systemuser"?"systemusers":"teams";return await r.updateRecord(e,t,{"ownerid@odata.bind":`/${l}(${o})`}),u(`Assigned record ${t} in ${e} to new owner`,{message:"Record assigned successfully"},["Use dataverse_list_users or dataverse_list_teams to find valid owners"])}default:throw new Error(`Unknown CRUD tool: ${a}`)}}import{z as S}from"zod";var He=[{name:"dataverse_associate",description:"Creates an association between two Dataverse records via a named N:N or 1:N relationship. Requires the relationship schema name obtainable from dataverse_get_relationships. Use for N:N relationships or to link records without modifying a lookup field directly \u2014 for simple 1:N lookups, setting the lookup field in dataverse_update is simpler. WHEN TO USE: Linking two records via an N:N relationship or 1:N navigation property. BEST PRACTICES: Get the relationship schema name from dataverse_get_relationships first; for simple 1:N lookups use dataverse_update. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Source record GUID"},relationshipName:{type:"string",description:"Relationship schema name"},relatedEntitySetName:{type:"string",description:"Related entity set name"},relatedId:{type:"string",description:"Related record GUID"}},required:["entitySetName","id","relationshipName","relatedEntitySetName","relatedId"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_associate_bulk",description:"Associates one source record with multiple related records at once via a named relationship, executing all associations in parallel. Unlike dataverse_associate (one pair at a time), this accepts an array of related IDs and runs them concurrently. Uses Promise.allSettled semantics \u2014 individual failures are reported per item without aborting the others. WHEN TO USE: Bulk N:N association setup (e.g., assigning many privileges to a role, linking many contacts to a campaign). BEST PRACTICES: Check existing associations first with dataverse_query; runs concurrently so order is not guaranteed. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"Source entity set name"},id:{type:"string",description:"Source record GUID"},relationshipName:{type:"string",description:"Relationship schema name"},relatedEntitySetName:{type:"string",description:"Related entity set name"},relatedIds:{type:"array",items:{type:"string"},description:"GUIDs of records to associate (1\u2013200)",minItems:1,maxItems:200}},required:["entitySetName","id","relationshipName","relatedEntitySetName","relatedIds"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_disassociate",description:"Removes an existing association between two Dataverse records on a named relationship. For N:N relationships, provide relatedId and relatedEntitySetName to build the correct $id URL. For 1:N relationships, relatedId and relatedEntitySetName are optional. Use dataverse_get_relationships to find the correct relationship schema name. WHEN TO USE: Removing an N:N or 1:N link between two records without deleting either record. BEST PRACTICES: Get the relationship schema name from dataverse_get_relationships; relatedId is required for N:N. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string"},relationshipName:{type:"string"},relatedId:{type:"string",description:"Required for N:N relationships"},relatedEntitySetName:{type:"string",description:'Entity set name of the related record (required for N:N). E.g., "contacts"'},confirm:{type:"boolean",const:!0,description:"Must be explicitly true to confirm removal of the association"}},required:["entitySetName","id","relationshipName","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_query_associations",description:"Reads existing N:N associations for a record by querying through a navigation property. Returns the related records \u2014 use to verify what's already linked before calling dataverse_associate or dataverse_disassociate. WHEN TO USE: Check if a role already has specific privileges before adding them; check campaign members; check group participants. BEST PRACTICES: Use $select to return only needed columns; set $top to limit results. Example: to check role privileges, query entitySetName='roles', navigationProperty='roleprivileges_association' with $select='name,privilegeid'. WORKFLOW: read_record, inspect_audit.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:"Source entity set name, e.g. 'roles'"},id:{type:"string",description:"Source record GUID"},navigationProperty:{type:"string",description:"Navigation property name, e.g. 'roleprivileges_association'"},select:{type:"string",description:"Comma-separated columns to return on related records"},top:{type:"number",description:"Max records to return. Default 50, max 1000"},filter:{type:"string",description:"OData $filter expression applied to related records"}},required:["entitySetName","id","navigationProperty"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],va=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedEntitySetName:h,relatedIds:S.array(S.string().uuid()).min(1).max(200)}),ha=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedEntitySetName:h,relatedId:S.string().uuid()}),ba=S.object({entitySetName:h,id:S.string().uuid(),relationshipName:ae,relatedId:S.string().uuid().optional(),relatedEntitySetName:h.optional(),confirm:S.literal(!0)}),_a=/^[A-Za-z_][A-Za-z0-9_]*$/,wa=S.object({entitySetName:h,id:S.string().uuid(),navigationProperty:S.string().min(1).regex(_a,"Invalid navigation property name"),select:S.string().optional(),top:S.number().int().min(1).max(1e3).optional(),filter:S.string().optional()});async function $e(a,i,r){switch(a){case"dataverse_associate_bulk":{let{entitySetName:e,id:t,relationshipName:n,relatedEntitySetName:o,relatedIds:l}=va.parse(i),s=await Promise.allSettled(l.map(p=>r.associate(e,t,n,o,p))),d=[],c=[];return s.forEach((p,m)=>{p.status==="fulfilled"?d.push(l[m]):c.push({id:l[m],error:p.reason instanceof Error?p.reason.message:String(p.reason)})}),u(`${d.length}/${l.length} associations succeeded`,{entitySetName:e,id:t,relationshipName:n,succeeded:d,failed:c,successCount:d.length,failureCount:c.length},c.length>0?["Some associations failed \u2014 check the 'failed' array for details"]:["All associations processed (Dataverse returns 204 for both new and duplicate associations \u2014 use dataverse_query_associations to verify current state)"])}case"dataverse_associate":{let{entitySetName:e,id:t,relationshipName:n,relatedEntitySetName:o,relatedId:l}=ha.parse(i);return await r.associate(e,t,n,o,l),u(`Associated ${e}(${t}) with ${o}(${l}) via ${n}`,{message:"Records associated successfully"},["Use dataverse_get_relationships to verify relationship names"])}case"dataverse_disassociate":{let{entitySetName:e,id:t,relationshipName:n,relatedId:o,relatedEntitySetName:l}=ba.parse(i);return await r.disassociate(e,t,n,o,l),u(`Disassociated records via ${n}`,{message:"Records disassociated successfully"},["This removes the N:N link but does not delete records"])}case"dataverse_query_associations":{let{entitySetName:e,id:t,navigationProperty:n,select:o,top:l,filter:s}=wa.parse(i),d=`${e}(${t})/${n}`,p=(await r.query(d,{...o?{select:o.split(",").map(m=>m.trim())}:{},top:l??50,...s?{filter:s}:{}})).value??[];return u(`${p.length} associated records via ${n}`,{entitySetName:e,id:t,navigationProperty:n,count:p.length,records:p},["Use dataverse_associate to add new associations","Use dataverse_disassociate to remove existing ones"])}default:throw new Error(`Unknown relation tool: ${a}`)}}import{z as b}from"zod";var Me=[{name:"dataverse_execute_action",description:"Executes a global (unbound) Dataverse action that is not tied to a specific record \u2014 for example WinOpportunity, SendEmail, or custom process actions. Use dataverse_execute_bound_action when the action must operate on a particular record. Actions differ from functions in that they are state-changing operations; for read-only operations use dataverse_execute_function. WHEN TO USE: Invoking global Dataverse actions or custom process actions like WinOpportunity or SendEmail. BEST PRACTICES: Check action parameters via metadata or docs; use dataverse_execute_bound_action for record-scoped actions. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{actionName:{type:"string",description:'Action logical name (e.g., "WinOpportunity")'},parameters:{type:"object",description:"Action parameters"}},required:["actionName"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_execute_function",description:"Executes a global (unbound) Dataverse OData function that is read-only and returns data without side effects \u2014 for example RetrieveTotalRecordCount or InitializeFrom. Use dataverse_execute_action for state-changing operations. Use dataverse_execute_bound_action when the function/action requires a specific record context. WHEN TO USE: Calling global read-only OData functions like RetrieveTotalRecordCount or InitializeFrom. BEST PRACTICES: Functions are side-effect-free and safe to retry; use dataverse_execute_action for mutations. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{functionName:{type:"string",description:'Function name (e.g., "WhoAmI", "RetrieveTotalRecordCount")'},parameters:{type:"object",description:"Function parameters: strings are quoted inline, numbers/booleans inlined as-is, objects serialized as aliased OData parameters"}},required:["functionName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_bound_action",description:"Executes a Dataverse action bound to a specific record instance, passing the entity set name and record GUID as context (e.g., QualifyLead on a lead, or a custom action scoped to an account). The actionName should not include the Microsoft.Dynamics.CRM namespace prefix. Use dataverse_execute_action for global unbound actions that do not require a record context. WHEN TO USE: Running an action scoped to a specific record, e.g. QualifyLead on a lead. BEST PRACTICES: Omit the Microsoft.Dynamics.CRM namespace prefix; ensure the record exists first. WORKFLOW: update_record.",inputSchema:{type:"object",properties:{entitySetName:{type:"string"},id:{type:"string",description:"Record GUID"},actionName:{type:"string",description:"Action name (without Microsoft.Dynamics.CRM prefix)"},parameters:{type:"object",description:"Action parameters"}},required:["entitySetName","id","actionName"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_retrieve_dependencies_for_delete",description:"Checks what solution components would block deletion of a specific component. Provide the Dataverse component type code (1=Entity, 2=Attribute, 26=SavedQuery, 29=Workflow, 92=PluginAssembly) and the component GUID. Use before deleting shared Dataverse customization components. WHEN TO USE: Before deleting a solution component to check for blocking dependencies. BEST PRACTICES: Resolve all dependencies before attempting deletion. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{componentType:{type:"number",description:"Dataverse component type code (1=Entity, 2=Attribute, 29=Workflow, 92=PluginAssembly)"},objectId:{type:"string",description:"Component GUID"}},required:["componentType","objectId"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_execute_bound_function",description:"Executes a Dataverse function bound to a specific record (e.g., CalculateRollupField, GetQuantityAvailable). Use for read-only computed operations on a single record. Unlike bound actions, bound functions do not modify data. WHEN TO USE: Calling a read-only computed function on a specific record, e.g. CalculateRollupField. BEST PRACTICES: Functions are side-effect-free and safe to call repeatedly; pass parameters as string key-value pairs. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name of the table (e.g., "accounts")'},id:{type:"string",description:"GUID of the record"},functionName:{type:"string",description:'Name of the bound function (e.g., "CalculateRollupField")'},parameters:{type:"object",description:"Function parameters as key-value pairs of strings",additionalProperties:{type:"string"}}},required:["entitySetName","id","functionName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_dependencies",description:"Lists workflows, Power Automate flows, Business Rules, and custom actions that reference a given table. Use to detect hidden dependencies before modifying or removing a table. Returns component name, type, state (Active/Draft), trigger event (Create/Update/Delete), and count. WHEN TO USE: Before modifying or removing a table, to discover workflows, flows, and plugins that reference it. BEST PRACTICES: Filter by componentType to focus on specific dependency kinds. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{tableName:{type:"string",description:'Logical name of the table to check (e.g., "account", "contact")'},componentType:{type:"array",items:{type:"string",enum:["Workflow","Flow","BusinessRule","Action","BusinessProcessFlow","Plugin","CustomAPI"]},description:"Filter by component type. Default: all types."}},required:["tableName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ne=/^[a-zA-Z0-9_.]+$/,Sa=b.object({actionName:b.string().min(1).regex(ne,"actionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Ta=b.object({functionName:b.string().min(1).regex(ne,"functionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Na=b.object({entitySetName:h,id:b.string().uuid(),actionName:b.string().min(1).regex(ne,"actionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})}),Ra=b.object({componentType:b.number().int().positive(),objectId:b.string().uuid()}),Aa=["Workflow","Flow","BusinessRule","Action","BusinessProcessFlow","Plugin","CustomAPI"],Ea=b.object({tableName:b.string().min(1),componentType:b.array(b.enum(Aa)).optional()}),xa=b.object({entitySetName:h,id:b.string().uuid(),functionName:b.string().min(1).regex(ne,"functionName must contain only letters, digits, underscores, or dots"),parameters:b.record(b.unknown()).optional().default({})});async function We(a,i,r){switch(a){case"dataverse_execute_action":{let{actionName:e,parameters:t}=Sa.parse(i),n=await r.executeAction(e,t);return u(`Executed unbound action ${e}`,n,["Use dataverse_list_custom_actions to discover available actions"])}case"dataverse_execute_function":{let{functionName:e,parameters:t}=Ta.parse(i),n=await r.executeFunction(e,t);return u(`Executed function ${e}`,n,["Functions are read-only operations"])}case"dataverse_execute_bound_action":{let{entitySetName:e,id:t,actionName:n,parameters:o}=Na.parse(i),l=await r.executeBoundAction(e,t,n,o);return u(`Executed bound action ${n} on ${e}(${t})`,l)}case"dataverse_list_dependencies":{let{tableName:e,componentType:t}=Ea.parse(i),n=await r.listTableDependencies(e,t);return u(`${n.count} dependencies found for component`,n,["Use this information before deleting or modifying the component"])}case"dataverse_retrieve_dependencies_for_delete":{let{componentType:e,objectId:t}=Ra.parse(i),n=await r.listDependencies(e,t),o=Array.isArray(n)?n:n.value??[n];return u(`${o.length} dependencies found`,n,["Review dependencies before deleting the component"])}case"dataverse_execute_bound_function":{let{entitySetName:e,id:t,functionName:n,parameters:o}=xa.parse(i),l=await r.executeBoundFunction(e,t,n,o);return u(`Executed bound function ${n} on ${e}(${t})`,l)}default:throw new Error(`Unknown action tool: ${a}`)}}import{z as $}from"zod";var je=[{name:"dataverse_batch_execute",description:`Executes multiple Dataverse operations in a single HTTP $batch request to reduce network round-trips and improve throughput. Accepts up to 1000 individual GET, POST, PATCH, or DELETE requests. Use for bulk creates, updates, or deletes that need to be grouped for performance. Set useChangeset=true to wrap all mutating operations (POST/PATCH/DELETE) in an atomic changeset \u2014 a failure rolls back ALL changeset operations. Individual per-operation results (status, body) are returned as an array in the same order as the input requests. $REF OPERATIONS (N:N/1:N associations): POST to a collection-valued navigation property (N:N): url='roles({id})/roleprivileges_association/$ref', body must contain { "@odata.id": "https://<org_url>/api/data/v9.2/privileges({privilegeId})" } with an ABSOLUTE URL \u2014 a relative URL will cause a 400 error. PUT to a single-valued navigation property (lookup/1:N): url='contacts({id})/account_primary_contact/$ref', body must contain { "@odata.id": "https://<org_url>/api/data/v9.2/accounts({accountId})" } with an ABSOLUTE URL. Example: { method: 'POST', url: 'roles({roleId})/roleprivileges_association/$ref', body: { '@odata.id': 'https://org.crm.dynamics.com/api/data/v9.2/privileges({privilegeId})' } }. WHEN TO USE: Bulk creates, updates, or deletes (up to 1000 ops) needing a single HTTP round-trip; batched $ref associations. BEST PRACTICES: Use useChangeset=true for atomic all-or-nothing mutations; batch GETs for parallel reads; always use absolute URLs in @odata.id for $ref bodies. WORKFLOW: bulk_operations.`,inputSchema:{type:"object",properties:{requests:{type:"array",description:"Array of batch requests to execute",items:{type:"object",properties:{method:{type:"string",enum:["GET","POST","PATCH","DELETE"],description:"HTTP method"},url:{type:"string",description:'Relative URL (e.g., "accounts(guid)" or "contacts"). For $ref operations (e.g. "roles({id})/roleprivileges_association/$ref"), the request body MUST include "@odata.id" with an ABSOLUTE URL \u2014 relative URLs in @odata.id cause 400 errors.'},body:{type:"object",description:"Request body for POST/PATCH operations"}},required:["method","url"]}},useChangeset:{type:"boolean",description:"Wrap mutating operations (POST/PATCH/DELETE) in an atomic changeset. A failure rolls back ALL operations in the changeset. Defaults to false."}},required:["requests"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}}],ka=$.object({method:$.enum(["GET","POST","PATCH","DELETE"]),url:$.string().min(1).refine(a=>!/[\r\n]/.test(a),{message:"Batch URL must not contain CR or LF characters"}).refine(a=>!a.startsWith("http"),{message:"Batch URL must be a relative path, not an absolute URL"}).refine(a=>!/(\.\.[\/\\])|(^\.\.$)/.test(a),{message:"Batch URL must not contain path traversal sequences"}),body:$.record($.unknown()).optional()}),Ia=$.object({requests:$.array(ka).min(1).max(1e3),useChangeset:$.boolean().default(!1).describe("Wrap mutating operations (POST/PATCH/DELETE) in an atomic changeset. A failure rolls back ALL operations in the changeset.")});async function Fe(a,i,r,e){if(a==="dataverse_batch_execute"){let{requests:t,useChangeset:n}=Ia.parse(i),o=t.map(d=>({method:d.method,url:d.url,body:d.body}));await e?.report(0,t.length);let l=await r.batchExecute(o,n);await e?.report(t.length,t.length);let s=l.filter(d=>d===null||!d.error).length;return u(`Batch executed: ${s}/${l.length} operations succeeded`,{results:l,count:l.length},["Use batch for bulk create/update operations to reduce HTTP round-trips"])}throw new Error(`Unknown batch tool: ${a}`)}import{z as re}from"zod";var Be=[{name:"dataverse_change_detection",description:"Detects new, modified, and deleted records since a previous sync using Dataverse change tracking (delta queries). On first call, pass deltaToken=null to get an initial snapshot and receive a token. On subsequent calls, pass the returned token to retrieve only changes since last sync. Change tracking must be enabled on the table in Dataverse settings. Returns newAndModified records, deleted record IDs, and the nextDeltaToken for the next call. WHEN TO USE: Incremental sync \u2014 detecting records created, updated, or deleted since a previous snapshot. BEST PRACTICES: Store the deltaToken persistently; always specify $select to minimize payload. WORKFLOW: query_data.",inputSchema:{type:"object",properties:{entitySetName:{type:"string",description:'OData entity set name (e.g., "accounts")'},deltaToken:{anyOf:[{type:"string"},{type:"null"}],description:"Delta token from a previous call, or null for the initial sync"},select:{type:"array",items:{type:"string"},description:"Columns to return (recommended to minimise payload)"}},required:["entitySetName","deltaToken"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Da=re.object({entitySetName:h,deltaToken:re.string().nullable(),select:re.array(re.string()).optional()});async function Ge(a,i,r){if(a==="dataverse_change_detection"){let{entitySetName:e,deltaToken:t,select:n}=Da.parse(i),o;try{o=await r.getChangedRecords(e,t,n)}catch(s){let d=s instanceof Error?s.message:String(s);if(/change.?track|0x80072491/i.test(d))return X({type:"feature_disabled",feature:"Change Tracking",cannotProceedBecause:`Change tracking is not enabled on '${e}', so delta queries cannot be executed.`,adminPortal:"Power Apps Maker Portal",steps:["Open Power Apps maker portal (make.powerapps.com)",`Navigate to Tables \u2192 search for '${e}'`,"Open the table \u2192 click the Settings (gear) icon",'Enable "Track changes"',"Save the table, then publish customizations"],fixableViaToolName:"dataverse_update_entity"});throw s}let l=Array.isArray(o?.newAndModified)?o.newAndModified:[];return u(`${l.length} changed records detected`,o,["Store the returned deltaToken for subsequent incremental sync"])}throw new Error(`Unknown tracking tool: ${a}`)}import{z as M}from"zod";var Oa=M.object({components:M.object({entities:M.array(M.string()).optional().describe("Entity logical names to publish"),webResources:M.array(M.string()).optional().describe("Web resource names to publish"),optionSets:M.array(M.string()).optional().describe("Global OptionSet names to publish")}).optional().describe("Specific components to publish. If omitted, ALL unpublished customizations are published.")}),ze=[{name:"dataverse_publish_customizations",description:'Publishes unpublished Dataverse customizations. \u26A0\uFE0F WITHOUT components \u2192 calls PublishAllXml, an EXCLUSIVE ORG-WIDE LOCK lasting 30-120s. While active, ALL other publish calls (scoped or global) fail with "already in progress". \u2705 ALWAYS prefer specifying components.entities/webResources/optionSets to use the scoped PublishXml action instead \u2014 faster and does NOT block other operations. \u274C DO NOT call without components after each individual change \u2014 batch ALL your schema changes first, then publish ONCE at the end. If you receive a "publish already in progress" error, the MCP will auto-retry every 30s (up to 2 minutes) \u2014 do NOT call the tool again; wait for the previous call to return. WHEN TO USE: Once at the end of a series of schema changes (table/column/relationship/form creation). BEST PRACTICES: Pass components.entities with the list of modified entity logical names. Full publish only if you cannot enumerate the changed components. WORKFLOW: manage_solution.',inputSchema:{type:"object",properties:{components:{type:"object",description:"Specific components. Omit to publish all.",properties:{entities:{type:"array",items:{type:"string"}},webResources:{type:"array",items:{type:"string"}},optionSets:{type:"array",items:{type:"string"}}}}},required:[]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}];async function Ve(a,i,r){switch(a){case"dataverse_publish_customizations":{let{components:e}=Oa.parse(i??{}),t=await r.publishCustomizations(e);return u("Customizations published successfully",t,["Changes are now visible to all users in the environment"])}default:throw new Error(`Unknown solution tool: ${a}`)}}import{z as Z}from"zod";var Ca=Z.object({callerId:Z.string().uuid().describe('Azure AD Object ID (GUID) of the Dataverse system user to impersonate. Requires the executing account to have the "Act on behalf of another user" privilege in Dataverse.'),toolName:Z.string().min(1).describe('Name of the MCP tool to execute on behalf of the user (e.g., "dataverse_create", "dataverse_query")'),toolArgs:Z.record(Z.unknown()).describe("Arguments for the wrapped tool, as an object")}),ue=[{name:"dataverse_impersonate",description:'Executes another Dataverse tool on behalf of a different system user by injecting the MSCRMCallerId header. Requires the executing account to have the "Act on behalf of another user" privilege in Dataverse. The impersonation applies ONLY to the single tool call specified. Use for auditing workflows that must create or update records under a specific user identity. WHEN TO USE: Creating or updating records under a specific user identity for audit-trail purposes. BEST PRACTICES: Impersonation applies to the single wrapped tool call only; cannot impersonate System Administrators. WORKFLOW: update_record.',inputSchema:{type:"object",properties:{callerId:{type:"string",description:"GUID (Azure AD Object ID) of the Dataverse system user to impersonate"},toolName:{type:"string",description:'MCP tool to execute while impersonating (e.g., "dataverse_create")'},toolArgs:{type:"object",description:"Arguments for the wrapped tool"}},required:["callerId","toolName","toolArgs"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}}];async function Ke(a,i,r,e){if(a!=="dataverse_impersonate")throw new Error(`Unknown impersonate tool: ${a}`);let{callerId:t,toolName:n,toolArgs:o}=Ca.parse(i);try{if(((await r.query(`systemusers(${v(t)})/systemuserroles_association`,{select:["name"],filter:"name eq 'System Administrator'",top:1})).value?.length??0)>0)throw new Error("Security policy: impersonation of users with System Administrator role is prohibited to prevent privilege escalation.")}catch(d){throw d instanceof Error&&d.message.includes("Security policy")?d:new Error(`Security policy: cannot verify callerId roles \u2014 impersonation denied. Cause: ${String(d)}`)}let l=r.http,s=l.defaultHeaders.MSCRMCallerId;try{l.defaultHeaders.MSCRMCallerId=t;let d=await e(n,o,r);return u(`Impersonated as ${t}, executed ${n}`,{impersonatedAs:t,tool:n,result:JSON.parse(d.content[0].text)},["Impersonation applies to this single call only"])}finally{s===void 0?delete l.defaultHeaders.MSCRMCallerId:l.defaultHeaders.MSCRMCallerId=s}}import{z as A}from"zod";var Ua={10:"Pre-validation",20:"Pre-operation",40:"Post-operation",45:"Post-operation (deprecated)"},La={0:"Synchronous",1:"Asynchronous"},Pa=A.object({workflowId:A.string().uuid(),activate:A.boolean()}),Xe=[{name:"dataverse_list_custom_actions",description:"Lists all custom actions (custom API / SDK messages) registered in the environment. Returns the message name, category, bound entity (if any), execute privilege, and whether it is customizable. Useful for discovering available automation entry points and agent-callable actions. WHEN TO USE: Discovering available custom API / SDK messages for automation. BEST PRACTICES: Use nameFilter to search; follow up with dataverse_execute_action to invoke. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 100, max 500)"},nameFilter:{type:"string",description:"Filter by name (substring match)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_plugin_steps",description:"Lists plugin steps (SdkMessageProcessingStep registrations) in the environment. Shows plugin assembly, step name, message (Create/Update/Delete/\u2026), entity, stage (pre/post), mode (sync/async), and state (enabled/disabled). Essential for understanding what custom business logic fires on Dataverse operations. WHEN TO USE: Understanding what custom business logic fires on CRUD operations for a table. BEST PRACTICES: Filter by entityLogicalName; check stage and mode to understand execution order and timing. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 100, max 500)"},activeOnly:{type:"boolean",description:"Return only enabled steps (default: true)"},entityLogicalName:{type:"string",description:"Filter by entity logical name (e.g. 'account')"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_set_workflow_state",description:"Activates or deactivates a Dataverse workflow (classic workflow / real-time workflow / action). Set activate=true to activate (statecode 1, statuscode 2) or activate=false to deactivate (statecode 0, statuscode 1). Returns the new state. WHEN TO USE: Activating or deactivating a classic workflow, real-time workflow, or action. BEST PRACTICES: Verify workflow ID first; deactivation stops future triggers but does not cancel in-progress runs. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{workflowId:{type:"string",description:"The workflow GUID"},activate:{type:"boolean",description:"true = activate, false = deactivate (draft)"}},required:["workflowId","activate"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_list_connection_references",description:"Lists all Power Automate connection references defined in the environment, showing their display name, connector ID, connection ID, and active status. Use this before importing a solution containing flows to detect broken or unmapped connections that would cause silent import failures. WHEN TO USE: Pre-deployment solution validation, auditing connection health, identifying broken flow connections. BEST PRACTICES: Look for inactive (isActive=false) references \u2014 these indicate flows that will fail after deployment. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{connectorId:{type:"string",description:"Filter by connector ID substring (e.g. '/providers/Microsoft.PowerApps/apis/shared_sharepointonline')"},activeOnly:{type:"boolean",description:"Return only active connection references (default: false = return all)"},top:{type:"number",description:"Maximum records to return (default 100, max 500)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],qa=A.object({top:A.number().positive().max(500).optional().default(100),nameFilter:A.string().optional()}),Ha=A.object({top:A.number().positive().max(500).optional().default(100),activeOnly:A.boolean().optional().default(!0),entityLogicalName:A.string().optional()}),$a=A.object({connectorId:A.string().optional(),activeOnly:A.boolean().optional().default(!1),top:A.number().int().positive().max(500).optional().default(100)});async function Qe(a,i,r){switch(a){case"dataverse_list_custom_actions":{let{top:e,nameFilter:t}=qa.parse(i??{}),n=["isprivate eq false"];t&&n.push(`contains(name,'${v(t)}')`);let l=(await r.query("sdkmessages",{select:["sdkmessageid","name","categoryname","isprivate","isreadonly","isvalidforexecuteasync"],filter:n.join(" and "),top:e})).value.map(s=>({id:s.sdkmessageid,name:s.name,category:s.categoryname??"",isPrivate:s.isprivate,isReadOnly:s.isreadonly,asyncSupported:s.isvalidforexecuteasync}));return u(`${l.length} custom actions found`,{total:l.length,messages:l},["Use dataverse_execute_action to run an action"])}case"dataverse_list_plugin_steps":{let{top:e,activeOnly:t,entityLogicalName:n}=Ha.parse(i??{}),o={select:["sdkmessageprocessingstepid","name","stage","mode","rank","statecode","filteringattributes","asyncautodelete"],expand:"sdkmessageid($select=name,categoryname),plugintypeid($select=name,assemblyname),sdkmessagefilterid($select=primaryobjecttypecode)",top:e};t&&(o.filter="statecode eq 0");let s=(await r.query("sdkmessageprocessingsteps",o)).value;if(n){let c=n.toLowerCase();s=s.filter(p=>p.sdkmessagefilterid?.primaryobjecttypecode?.toLowerCase()===c)}let d=s.map(c=>({id:c.sdkmessageprocessingstepid,name:c.name,message:c.sdkmessageid?.name??"",entity:c.sdkmessagefilterid?.primaryobjecttypecode??"",assembly:c.plugintypeid?.assemblyname??"",pluginType:c.plugintypeid?.name??"",stage:c.stage,stageName:Ua[c.stage]??`Stage ${c.stage}`,mode:c.mode,modeName:La[c.mode]??`Mode ${c.mode}`,rank:c.rank,isActive:c.statecode===0,filteringAttributes:c.filteringattributes??null,asyncAutoDelete:c.asyncautodelete}));return u(`${d.length} plugin steps found`,{total:d.length,steps:d},["Use dataverse_get_plugin_trace_logs for debugging plugin issues"])}case"dataverse_set_workflow_state":{let{workflowId:e,activate:t}=Pa.parse(i);try{await r.updateRecord("workflows",e,{statecode:t?1:0,statuscode:t?2:1})}catch(n){let o=n instanceof Error?n.message:String(n);throw/0x80040203|does not refer to a valid workflow|404/i.test(o)?new Error(`Workflow '${e}' not found. Use dataverse_list_workflows to retrieve valid workflow GUIDs in this environment. Original error: ${o}`):n}return u(`Workflow state updated for ${e}`,{workflowId:e,newState:t?"Activated":"Draft",statecode:t?1:0,statuscode:t?2:1})}case"dataverse_list_connection_references":{let{connectorId:e,activeOnly:t,top:n}=$a.parse(i??{}),o=[];t&&o.push("statecode eq 0"),e&&o.push(`contains(connectorid,'${v(e)}')`);let l={select:["connectionreferenceid","connectionreferencelogicalname","connectionreferencedisplayname","connectorid","connectionid","statecode","statuscode"],orderby:"connectionreferencedisplayname asc",top:n};o.length&&(l.filter=o.join(" and "));let d=((await r.query("connectionreferences",l)).value??[]).map(m=>({id:m.connectionreferenceid??"",logicalName:m.connectionreferencelogicalname??"",displayName:m.connectionreferencedisplayname??"",connectorId:m.connectorid??"",connectionId:m.connectionid??null,isActive:m.statecode===0,statusCode:m.statuscode??null})),c=d.filter(m=>!m.isActive).length,p=c>0?`${d.length} connection references found (${c} inactive \u2014 check before deploying)`:`${d.length} connection references found`;return u(p,{connectionReferences:d,count:d.length,inactiveCount:c},["Inactive references (isActive=false) indicate broken connections that will cause flow failures","Use the Power Apps maker portal to reconnect or replace broken connection references before deploying"])}default:throw new Error(`Unknown customization tool: ${a}`)}}import{z as x}from"zod";var Ma={1e8:"String",100000001:"Number",100000002:"Boolean",100000003:"JSON",100000004:"DataSource"},Wa={String:1e8,Integer:100000001,Boolean:100000002,JSON:100000003},Ye=[{name:"dataverse_get_environment_variable",description:"Retrieves an environment variable's definition and current value from Dataverse. Returns the schema name, display name, type, default value, and the current override value (if set). Useful for reading feature flags, configuration values, and integration settings stored in Dataverse environment variables. WHEN TO USE: Reading configuration values, feature flags, or integration settings stored as environment variables. BEST PRACTICES: Check both defaultValue and currentValue; the effective value is currentValue ?? defaultValue. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"The schema name of the environment variable (e.g. 'new_MyConfig')"}},required:["schemaName"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_set_environment_variable",description:"Sets or updates an environment variable value in Dataverse. If a value record already exists for the variable, it is updated; otherwise a new value record is created. The schemaName must match an existing environment variable definition. WHEN TO USE: Updating configuration values or feature flags stored in Dataverse environment variables. BEST PRACTICES: Verify the variable exists first with dataverse_get_environment_variable; schemaName must match exactly. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"The schema name of the environment variable"},value:{type:"string",description:"The new value to set"}},required:["schemaName","value"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_create_environment_variable",description:"Creates a new Dataverse environment variable definition and sets its initial value. Use this when the variable does not yet exist \u2014 use dataverse_set_environment_variable to update an existing one. WHEN TO USE: Initial setup of configuration flags, feature toggles, or integration settings. BEST PRACTICES: Use a solution-prefixed schemaName (e.g. 'myprefix_MyConfig'); provide solutionUniqueName to register in a solution. WORKFLOW: manage_solution.",inputSchema:{type:"object",properties:{schemaName:{type:"string",description:"Schema name of the new variable (e.g. 'new_MyConfig'). Case-sensitive."},displayName:{type:"string",description:"Human-readable display name"},type:{type:"string",enum:["String","Integer","Boolean","JSON"],description:"Variable type"},value:{type:"string",description:"Initial value to set"},description:{type:"string",description:"Optional description"},defaultValue:{type:"string",description:"Optional default value (fallback when no override value is set)"},confirm:{type:"boolean",description:"Must be true \u2014 confirms intentional creation of a new environment variable definition"}},required:["schemaName","displayName","type","value","confirm"]},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},{name:"dataverse_environment_capabilities",description:"Returns a comprehensive snapshot of the Dataverse environment: identity (WhoAmI), organization settings (name, version, language, audit config), unmanaged solution count, and environment variable count. Use at the start of a session to orient yourself to the environment and its configuration. WHEN TO USE: Beginning of any Dataverse automation session, before bulk operations, or when debugging unexpected behavior. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ja=x.object({schemaName:x.string().min(1)}),Fa=x.object({schemaName:x.string().min(1),value:x.string()}),Ba=x.object({schemaName:x.string().min(1),displayName:x.string().min(1),type:x.enum(["String","Integer","Boolean","JSON"]),value:x.string(),description:x.string().optional(),defaultValue:x.string().optional(),confirm:x.literal(!0,{errorMap:()=>({message:"Set confirm: true to create a new environment variable definition"})})});async function Ga(a,i){let{schemaName:r}=ja.parse(a),t=(await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid","schemaname","displayname","description","type","defaultvalue","isrequired"],top:1})).value;if(t.length===0)throw new Error(`Environment variable '${r}' not found. Check the schema name (it is case-sensitive, e.g. 'new_MyConfig'). To browse existing variables: Power Apps maker portal \u2192 Solutions \u2192 your solution \u2192 Environment Variables. To create a new one: open a solution \u2192 New \u2192 More \u2192 Environment Variable.`);let n=t[0],o=n.environmentvariabledefinitionid,s=(await i.query("environmentvariablevalues",{filter:`_environmentvariabledefinitionid_value eq ${o}`,select:["environmentvariablevalueid","value"],top:1})).value,d=s.length>0?s[0]:null,c=n.type,p=d?d.value??null:null,m=n.defaultvalue??null,f={schemaName:n.schemaname,displayName:n.displayname??"",description:n.description??"",type:c,typeName:Ma[c]??"Unknown",defaultValue:m,currentValue:p,valueId:d?d.environmentvariablevalueid:null,isRequired:n.isrequired??!1,effectiveValue:p??m};return u(`Environment variable '${f.schemaName}': ${f.typeName} = ${f.effectiveValue??"(not set)"}`,f,["Use dataverse_set_environment_variable to update the value"])}async function za(a,i){let{schemaName:r,value:e}=Fa.parse(a),n=(await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid","schemaname"],top:1})).value;if(n.length===0)throw new Error(`Environment variable definition '${r}' not found. This tool can only update the value of an existing variable. To create a new environment variable: Power Apps maker portal \u2192 Solutions \u2192 your solution \u2192 New \u2192 More \u2192 Environment variable. Then call this tool to set its value.`);let o=n[0].environmentvariabledefinitionid,s=(await i.query("environmentvariablevalues",{filter:`_environmentvariabledefinitionid_value eq ${o}`,select:["environmentvariablevalueid","value"],top:1})).value,d,c;s.length>0?(c=s[0].environmentvariablevalueid,await i.updateRecord("environmentvariablevalues",c,{value:e}),d="updated"):(c=await i.createRecord("environmentvariablevalues",{value:e,"EnvironmentVariableDefinitionId@odata.bind":`/environmentvariabledefinitions(${o})`}),d="created");let p={schemaName:r,operation:d,valueId:c,value:e};return u(`Environment variable '${r}' set to new value`,p,["Use dataverse_get_environment_variable to verify the update"])}async function Va(a,i){let{schemaName:r,displayName:e,type:t,value:n,description:o,defaultValue:l}=Ba.parse(a),s=q({toolName:"dataverse_create_environment_variable"}).map(N=>`[${N.severity.toUpperCase()}] ${N.code}: ${N.message}`);if((await i.query("environmentvariabledefinitions",{filter:`schemaname eq '${v(r)}'`,select:["environmentvariabledefinitionid"],top:1})).value.length>0)throw new Error(`Environment variable '${r}' already exists. Use dataverse_set_environment_variable to update its value.`);let c=Wa[t]??1e8,p={schemaname:r,displayname:e,type:c};o&&(p.description=o),l!==void 0&&(p.defaultvalue=l);let m=await i.createRecord("environmentvariabledefinitions",p),f=await i.createRecord("environmentvariablevalues",{value:n,"EnvironmentVariableDefinitionId@odata.bind":`/environmentvariabledefinitions(${m})`});return u(`Environment variable '${r}' created (type: ${t}, value: '${n}').`,{schemaName:r,displayName:e,type:t,typeCode:c,definitionId:m,valueId:f,value:n,...s.length>0&&{warnings:s}},["Use dataverse_get_environment_variable to verify","Use dataverse_set_environment_variable to update the value later"])}async function Je(a,i,r){switch(a){case"dataverse_get_environment_variable":return Ga(i,r);case"dataverse_set_environment_variable":return za(i,r);case"dataverse_create_environment_variable":return Va(i,r);case"dataverse_environment_capabilities":{let[e,t,n,o]=await Promise.allSettled([r.executeFunction("WhoAmI",{}),r.query("organizations",{select:["uniquename","friendlyname","version","languagecode","isauditenabled"],top:1}),r.query("solutions",{filter:"ismanaged eq false",select:["uniquename"]}),r.query("environmentvariabledefinitions",{select:["schemaname"],top:1})]),l=e.status==="fulfilled"?e.value:null,s=t.status==="fulfilled"?t.value.value:[],d=n.status==="fulfilled"?n.value.value:[],c=o.status==="fulfilled"?o.value.value:[];return u("Environment capabilities retrieved",{identity:l,organization:s[0]??null,unmanagedSolutionsCount:d.length,environmentVariablesCount:c.length},["Use dataverse_list_solutions for full solution list","Use dataverse_get_environment_variable to read specific env vars"])}default:throw new Error(`Unknown environment tool: ${a}`)}}import{z as L}from"zod";var Ka={0:"Execute",1:"Create",2:"Retrieve",3:"RetrieveMultiple",4:"GetParent",5:"Update",6:"Delete",7:"Assign"},Xa={0:"Waiting",10:"WaitingForResources",20:"InProgress",21:"Pausing",22:"Canceling",30:"Succeeded",31:"Failed",32:"Canceled"},Qa={0:"ReadyToRun",1:"Suspended",2:"Locked",3:"Completed"},Ya=["plugintracelogid","typename","messagename","primaryentity","depth","operationtype","exceptiondetails","messageblock","createdon","performanceexecutionduration","correlationid","requestid"],Ja=["asyncoperationid","name","operationtype","statuscode","statecode","message","createdon","startedon","completedon"],Ze=[{name:"dataverse_get_plugin_trace_logs",description:"Retrieves recent plugin and custom workflow activity trace logs from Dataverse. Shows execution details including plugin type name, triggering message, entity, execution duration, trace messages written by the developer, and exception details if the plugin failed. Requires the Plugin Trace Log feature to be enabled in Dataverse settings (Settings > Administration > System Settings > Customization tab > 'Enable logging to plugin trace log'). Essential for debugging plugin failures in production. WHEN TO USE: Debugging plugin failures or performance issues in production. BEST PRACTICES: Enable Plugin Trace Log in Dataverse settings first; filter by plugin name or entity to narrow results. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records to return (default 50, max 200)"},pluginTypeFilter:{type:"string",description:"Filter by plugin type name (substring match, e.g. 'AccountValidation')"},messageFilter:{type:"string",description:"Filter by message name (e.g. 'Create', 'Update')"},entityFilter:{type:"string",description:"Filter by entity logical name (e.g. 'account')"},exceptionsOnly:{type:"boolean",description:"Return only traces where an exception occurred (default false)"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},{name:"dataverse_get_workflow_trace_logs",description:"Retrieves background workflow (Power Automate classic / legacy workflow engine) execution records from Dataverse. These are the AsyncOperation records for workflow-type operations, useful for diagnosing failures in background workflows and real-time workflows running asynchronously. Note: For modern cloud flows (Power Automate), use the Power Automate portal instead. WHEN TO USE: Diagnosing failures in classic/legacy background workflows. BEST PRACTICES: Use failedOnly=true to focus on errors; for modern cloud flows use Power Automate portal. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{top:{type:"number",description:"Max records (default 50, max 200)"},failedOnly:{type:"boolean",description:"Return only failed workflows (statuscode eq 31, default false)"},entityFilter:{type:"string",description:"Filter by regarding entity type (e.g. 'account')"}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],Za=L.object({top:L.number().int().positive().max(200).optional().default(50),pluginTypeFilter:L.string().optional(),messageFilter:L.string().optional(),entityFilter:L.string().optional(),exceptionsOnly:L.boolean().optional().default(!1)}),en=L.object({top:L.number().int().positive().max(200).optional().default(50),failedOnly:L.boolean().optional().default(!1),entityFilter:L.string().optional()});function U(a){return typeof a=="string"?a:""}function tn(a){return typeof a=="number"?a:null}async function et(a,i,r){switch(a){case"dataverse_get_plugin_trace_logs":{let e=Za.parse(i),t=[];e.pluginTypeFilter&&t.push(`contains(typename,'${v(e.pluginTypeFilter)}')`),e.messageFilter&&t.push(`messagename eq '${v(e.messageFilter)}'`),e.entityFilter&&t.push(`primaryentity eq '${v(e.entityFilter)}'`),e.exceptionsOnly&&t.push("exceptiondetails ne null");let n=t.length>0?t.join(" and "):void 0,s=((await r.query("plugintracelogs",{select:Ya,orderby:"createdon desc",top:e.top,...n!==void 0?{filter:n}:{}})).value??[]).map(c=>{let p=typeof c.operationtype=="number"?c.operationtype:0,m=c.exceptiondetails;return{id:U(c.plugintracelogid),typeName:U(c.typename),message:U(c.messagename),entity:U(c.primaryentity),depth:typeof c.depth=="number"?c.depth:0,operationType:p,operationTypeName:Ka[p]??String(p),createdOn:U(c.createdon),durationMs:tn(c.performanceexecutionduration),correlationId:U(c.correlationid),requestId:U(c.requestid),hasException:m!=null&&m!=="",exceptionDetails:typeof m=="string"?m:null,messageBlock:typeof c.messageblock=="string"?c.messageblock:null}}),d={total:s.length,logs:s};return u(`${s.length} plugin trace logs found`,d,["Filter by plugin name or correlation ID for specific traces"])}case"dataverse_get_workflow_trace_logs":{let e=en.parse(i),t=["operationtype eq 10"];e.failedOnly&&t.push("statuscode eq 31");let n=t.join(" and "),s=((await r.query("asyncoperations",{select:Ja,filter:n,orderby:"createdon desc",top:e.top})).value??[]).map(c=>{let p=typeof c.statuscode=="number"?c.statuscode:0,m=typeof c.statecode=="number"?c.statecode:0,f=c.message;return{id:U(c.asyncoperationid),name:U(c.name),statusCode:p,statusName:Xa[p]??String(p),stateCode:m,stateName:Qa[m]??String(m),createdOn:U(c.createdon),startedOn:typeof c.startedon=="string"?c.startedon:null,completedOn:typeof c.completedon=="string"?c.completedon:null,errorMessage:typeof f=="string"?f:null}}),d={total:s.length,workflows:s};return u(`${s.length} workflow trace logs found`,d,["Filter by status or entity type for specific workflow traces"])}default:throw new Error(`Unknown trace tool: ${a}`)}}import{z as E}from"zod";var tt=[{name:"dataverse_search",description:"Full-text Relevance Search across all configured Dataverse tables. Returns ranked results with entity name, record ID, score, and matched fields. Requires Relevance Search to be enabled in Dataverse admin settings. Use when you need to find records without knowing which table they belong to. WHEN TO USE: Cross-table free-text search when you don't know which table contains the data. BEST PRACTICES: Narrow with entities[] and $filter; use searchType=full for Lucene operators (AND, OR, NOT, wildcards). WORKFLOW: search_data.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Full-text search string (supports Lucene syntax with searchType=full)"},entities:{type:"array",items:{type:"string"},description:"Restrict to specific table logical names (omit to search all configured tables). Required when using select."},top:{type:"number",description:"Max results (default 10, max 50)"},searchMode:{type:"string",enum:["any","all"],description:"Match any or all terms (default: any)"},searchType:{type:"string",enum:["simple","full"],description:"Search syntax: simple (default) or full (enables Lucene syntax: AND, OR, NOT, wildcards, fuzzy, proximity, regex)"},filter:{type:"string",description:'OData $filter to apply on search results (e.g., "statecode eq 0")'},facets:{type:"array",items:{type:"string"},description:'Fields to return faceted counts for (e.g., ["entityname,count:100","statecode"])'},orderby:{type:"array",items:{type:"string"},description:'Result sort order (e.g., ["@search.score desc","modifiedon desc"])'},select:{type:"array",items:{type:"string"},description:"Columns to return per result. Only applies when entities is also specified."}},required:["query"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],an=E.object({query:E.string().min(1),entities:E.array(E.string()).optional(),top:E.number().int().positive().max(50).optional().default(10),searchMode:E.enum(["any","all"]).optional().default("any"),searchType:E.enum(["simple","full"]).optional(),filter:E.string().optional(),facets:E.array(E.string()).optional(),orderby:E.array(E.string()).optional(),select:E.array(E.string()).optional()});async function at(a,i,r){switch(a){case"dataverse_search":{let e=an.parse(i),t={search:e.query,top:e.top};if(e.entities?.length){let s=e.entities.map(d=>({name:d,...e.select?.length?{selectColumns:e.select}:{}}));t.entities=JSON.stringify(s)}e.searchType==="full"&&(t.options=JSON.stringify({querytype:"lucene"})),e.filter&&(t.filter=e.filter),e.facets?.length&&(t.facets=JSON.stringify(e.facets)),e.orderby?.length&&(t.orderby=JSON.stringify(e.orderby));let n;try{n=await r.search(t)}catch(s){let d=s instanceof Error?s.message:String(s);if(/404|not.?found|search.{0,20}disabl|relevance.?search|0x80048[Dd]0[Bb]|search.{0,20}not.{0,20}config|search.{0,20}unavail/i.test(d))return X({type:"feature_disabled",feature:"Dataverse Search (Relevance Search)",cannotProceedBecause:"Relevance Search is not enabled for this Dataverse environment, so full-text cross-table search is unavailable.",adminPortal:"Power Platform Admin Center",steps:["Open Power Platform Admin Center (admin.powerplatform.microsoft.com)","Select your environment \u2192 Settings","Navigate to Product \u2192 Features",'Under "Search", toggle "Dataverse Search" to On',"Save \u2014 indexing may take a few minutes before search is available"]});throw s}let o=(n.value??[]).map(s=>({entityName:s.entityname??"",objectId:s.objectid??"",score:s.score??0,highlights:s.highlights??{},fields:s.attributes??{}})),l={totalRecordCount:n.totalrecordcount??0,results:o};return n.facets&&(l.facets=n.facets),u(`${o.length} search results for '${e.query}'`,l,["Use dataverse_get to retrieve full record details","Narrow results with entities[] filter"])}default:throw new Error(`Unknown search tool: ${a}`)}}import{z as W}from"zod";var pe={1:"Create",2:"Update",3:"Delete",4:"Activate",5:"Deactivate",11:"Share",12:"Unshare",13:"Assign",104:"Access"},nn=Object.fromEntries(Object.entries(pe).map(([a,i])=>[i,Number(a)])),nt=[{name:"dataverse_get_audit_log",description:"Retrieves audit log entries from Dataverse. Returns operation details, user info, and parsed change data for each entry. At least one filter (recordId, entityLogicalName, userId, fromDate, or operations) is recommended to avoid large result sets. Audit must be enabled on the environment and table \u2014 returns a clear error if auditing is disabled (HTTP 403). WHEN TO USE: Tracking who changed what and when on Dataverse records. BEST PRACTICES: Always provide at least one filter; audit must be enabled on the table. WORKFLOW: inspect_audit.",inputSchema:{type:"object",properties:{recordId:{type:"string",description:"GUID of a specific record to retrieve audit entries for"},entityLogicalName:{type:"string",description:'Logical name of the entity to filter audit entries (e.g., "account", "contact")'},userId:{type:"string",description:"GUID of the user who made the changes"},fromDate:{type:"string",description:"ISO 8601 date string \u2014 only return audit entries created on or after this date"},top:{type:"number",description:"Maximum number of audit entries to return (default: 50, max: 500)"},operations:{type:"array",items:{type:"string"},description:'Filter by operation names: "Create", "Update", "Delete", "Activate", "Deactivate", "Share", "Unshare", "Assign", "Access"'}},required:[]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],rn=W.object({recordId:W.string().uuid().optional(),entityLogicalName:W.string().min(1).optional(),userId:W.string().uuid().optional(),fromDate:W.string().datetime({offset:!0}).optional(),top:W.number().positive().max(500).optional().default(50),operations:W.array(W.string().min(1)).optional()});function on(a){if(!a)return{};try{return JSON.parse(a)}catch{return a}}function sn(a){return{auditId:a.auditid,operation:a.operation,operationName:pe[a.operation]??`Unknown(${a.operation})`,action:a.action,actionName:pe[a.action]??`Unknown(${a.action})`,createdOn:a.createdon,userId:a._userid_value,userFullName:a.userid?.fullname??"",userDomainName:a.userid?.domainname??"",objectId:a._objectid_value,objectTypeCode:a.objecttypecode,changes:on(a.changedata)}}async function rt(a,i,r){switch(a){case"dataverse_get_audit_log":{let e=rn.parse(i),t=[];if(e.recordId&&t.push(`_objectid_value eq ${e.recordId}`),e.entityLogicalName){let n=v(e.entityLogicalName);t.push(`objecttypecode eq '${n}'`)}if(e.userId&&t.push(`_userid_value eq ${e.userId}`),e.fromDate&&t.push(`createdon ge ${e.fromDate}`),e.operations?.length){let n=e.operations.map(o=>nn[o]).filter(o=>o!==void 0);if(n.length>0){let o=n.map(l=>`action eq ${l}`).join(" or ");t.push(`(${o})`)}}try{let o=(await r.query("audits",{select:["auditid","action","operation","createdon","_objectid_value","objecttypecode","changedata","_userid_value"],...t.length>0?{filter:t.join(" and ")}:{},orderby:"createdon desc",top:e.top,expand:"userid($select=fullname,domainname)"})).value.map(sn);return u(`${o.length} audit records for ${e.entityLogicalName??e.recordId??"query"}`,{entries:o,count:o.length},["Filter by operation type for specific changes"])}catch(n){let o=n instanceof Error?n.message:String(n);if(o.includes("403")||o.includes("Forbidden"))return{content:[{type:"text",text:JSON.stringify({isError:!0,error:"Audit log access denied (HTTP 403). Ensure auditing is enabled on the Dataverse environment and the target table, and that the authenticated user has sufficient privileges."})}]};throw n}}default:throw new Error(`Unknown audit tool: ${a}`)}}import{z as Q}from"zod";var it=new Map,ot=[{name:"dataverse_detect_duplicates",description:"Checks for potential duplicate records before creating, using a FetchXML query that matches records sharing the same field values as the prospective record (OR conditions on the provided fields). Pass the prospective record fields to check against existing records. WHEN TO USE: Before creating a new record to check if a similar record already exists. BEST PRACTICES: Pass only the key identifying fields (e.g., name, email); too many fields will return fewer matches. WORKFLOW: create_record.",inputSchema:{type:"object",properties:{entityLogicalName:{type:"string",description:'Table to check, e.g., "account"'},record:{type:"object",description:"The prospective record fields to check for duplicates"},top:{type:"number",description:"Maximum number of duplicates to return (default 5, max 20)"}},required:["entityLogicalName","record"]},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}}],ln=Q.object({entityLogicalName:Q.string().min(1).regex(/^[a-z_][a-z0-9_]*$/i,"Must be a valid Dataverse logical name"),record:Q.record(Q.string(),Q.unknown()),top:Q.number().int().positive().max(20).optional().default(5)});async function st(a,i,r){switch(a){case"dataverse_detect_duplicates":{let n=function(m){return String(m).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")};var e=n;let t=ln.parse(i),o=Object.entries(t.record).filter(([,m])=>m!=null).map(([m,f])=>`<condition attribute="${n(m)}" operator="eq" value="${n(String(f))}" />`).join(`
|
|
3
3
|
`);if(!o)return u("No fields provided for duplicate detection",{hasDuplicates:!1,duplicateCount:0,duplicates:[]},["Provide at least one field value to check for duplicates"]);let l=it.get(t.entityLogicalName);l||(l=(await r.getTableMetadata(t.entityLogicalName,!1)).EntitySetName,it.set(t.entityLogicalName,l));let s=`<fetch top="${t.top}" distinct="true">
|
|
4
4
|
<entity name="${n(t.entityLogicalName)}">
|
|
5
5
|
<all-attributes />
|
|
@@ -46,9 +46,9 @@ ${a.map(n=>{let o=n.id??`Area_${++i}`,l=n.groups.map(s=>{let d=s.id??`Group_${++
|
|
|
46
46
|
- Use server-side filtering ($filter / FetchXML conditions) instead of client-side filtering.
|
|
47
47
|
- Cache table metadata \u2014 it rarely changes during a session.
|
|
48
48
|
`;function Gt(a,i,r,e){let t=Vt.getHandler(a);if(!t)throw new Error(`Unknown tool: ${a}`);return t(a,i,r,e)}function zt(a){let i=new Wn({name:"mcp-dataverse",version:Xt},{capabilities:{tools:{},resources:{}},instructions:Bt});return i.setRequestHandler(Bn,async()=>({tools:Kt})),i.setRequestHandler(Fn,async r=>{let{name:e,arguments:t}=r.params,n=r.params._meta?.progressToken,o=new J(n!=null?i:void 0,n);try{return Jn.has(e)?Ke(e,t,a,Gt):await Gt(e,t,a,o)}catch(l){return{content:[{type:"text",text:`Error: ${l instanceof Error?l.message:String(l)}`}],isError:!0}}}),i.setRequestHandler(Gn,async()=>({resources:kt()})),i.setRequestHandler(zn,async()=>({resourceTemplates:It()})),i.setRequestHandler(Vn,async r=>({contents:[await Dt(r.params.uri,a,Bt)]})),i}async function er(){let a=he(),i=be(a),r=new _e(i,a.maxRetries,a.requestTimeoutMs),e=we();if(e.transport==="http"){let{startHttpTransport:t}=await import("./http-server.js");process.stderr.write(`Starting HTTP transport on port ${e.port}...
|
|
49
|
-
`)
|
|
50
|
-
`)}catch(n
|
|
51
|
-
`)}await t(()=>zt(r),e.port,Xt,Kt.length)}else{let t=zt(r),n=new jn;await t.connect(n),process.stderr.write(`MCP Dataverse server started
|
|
49
|
+
`),i.getToken().then(()=>{process.stderr.write(`[mcp-dataverse] Authenticated \u2713
|
|
50
|
+
`)}).catch(n=>{let o=n instanceof Error?n.message:String(n);process.stderr.write(`[mcp-dataverse] Authentication failed: ${o}
|
|
51
|
+
`)}),await t(()=>zt(r),e.port,Xt,Kt.length)}else{let t=zt(r),n=new jn;await t.connect(n),process.stderr.write(`MCP Dataverse server started
|
|
52
52
|
`),i.getToken().then(()=>{process.stderr.write(`[mcp-dataverse] Authenticated \u2713
|
|
53
53
|
`)}).catch(o=>{let l=o instanceof Error?o.message:String(o);process.stderr.write(`[mcp-dataverse] Authentication failed: ${l}
|
|
54
54
|
`)})}}async function tr(){if(process.argv.includes("install")){let{runInstall:a}=await import("./install.js");await a(),process.exit(0)}if(process.argv.includes("doctor")){let{runDoctor:a}=await import("./doctor.js");await a()}await er()}tr().catch(a=>{process.stderr.write(`Fatal error: ${a instanceof Error?a.message:String(a)}
|
package/dist/setup-auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as n}from"./chunk-
|
|
2
|
+
import{a as n}from"./chunk-OQ46VPYS.js";import{a as s}from"./chunk-RYRO3QPE.js";import"./chunk-KJ3HM2VM.js";async function t(){process.stderr.write(`MCP Dataverse \u2014 One-time Authentication Setup
|
|
3
3
|
`),process.stderr.write(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4
4
|
`);let e=process.argv[2];e&&(process.env.DATAVERSE_ENV_URL=e);let r;try{r=n()}catch{process.stderr.write(`Environment URL is required.
|
|
5
5
|
Usage: npx mcp-dataverse-auth https://yourorg.crm.dynamics.com
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-dataverse",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The most complete MCP server for Microsoft Dataverse. 73 production-ready tools for AI agents: OData & FetchXML queries, CRUD, metadata, solutions, audit, batch operations and more.
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "The most complete MCP server for Microsoft Dataverse. 73 production-ready tools for AI agents: OData & FetchXML queries, CRUD, metadata, solutions, audit, batch operations and more. Device code, client credentials, and managed identity auth. Works with VS Code Copilot, Claude, Cursor, Windsurf and all MCP clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
7
7
|
"bin": {
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@azure/msal-node": "^2.16.0",
|
|
77
77
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
78
|
+
"jose": "^6.1.3",
|
|
78
79
|
"zod": "^3.23.0"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
package/server.json
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"source": "github",
|
|
19
19
|
"id": "1163599879"
|
|
20
20
|
},
|
|
21
|
-
"version": "0.
|
|
21
|
+
"version": "0.5.0",
|
|
22
22
|
"packages": [
|
|
23
23
|
{
|
|
24
24
|
"registryType": "npm",
|
|
25
25
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
26
26
|
"identifier": "mcp-dataverse",
|
|
27
|
-
"version": "0.
|
|
27
|
+
"version": "0.5.0",
|
|
28
28
|
"transport": {
|
|
29
29
|
"type": "stdio"
|
|
30
30
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a}from"./chunk-PAX4NW5B.js";import"./chunk-24RDOMG4.js";export{a as createAuthProvider};
|
package/dist/chunk-24RDOMG4.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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};
|
package/dist/chunk-PAX4NW5B.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as r}from"./chunk-24RDOMG4.js";function i(e){return new r(e.environmentUrl)}export{i as a};
|
package/dist/chunk-SUDI4JM6.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a}from"./chunk-SUDI4JM6.js";export{a as loadConfig};
|