mcp-dataverse 0.7.6 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CAPABILITIES.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP Dataverse Server — Complete Capabilities Reference
2
2
 
3
- > **Version**: 0.7.6 | **API Version**: Dataverse Web API v9.2 | **Transport**: stdio · HTTP/SSE
3
+ > **Version**: 0.7.7 | **API Version**: Dataverse Web API v9.2 | **Transport**: stdio · HTTP/SSE
4
4
 
5
5
  81 tools across 28 categories for full Dataverse lifecycle: schema, CRUD, FetchXML, solutions, plugins, audit, files, users, teams, RBAC, attribute management, environment variables, workflows, schema write, record access, web resources, and more.
6
6
 
package/README.md CHANGED
@@ -52,6 +52,14 @@ The interactive wizard configures your environment, registers the server in VS C
52
52
 
53
53
  > Requires Node.js 20+. For other clients (Claude, Cursor, Windsurf…) see [Multi-Client Setup](https://codeurali.github.io/mcp-dataverse/multi-client-setup).
54
54
 
55
+ ## Update
56
+
57
+ ```bash
58
+ npx mcp-dataverse update
59
+ ```
60
+
61
+ No re-config required — just checks your existing setup and shows what changed. `npx` auto-fetches the latest version on each run, so simply restarting your MCP client is enough to get the new tools.
62
+
55
63
  ---
56
64
 
57
65
  ## Authentication
package/dist/doctor.js CHANGED
@@ -1,3 +1,82 @@
1
1
  #!/usr/bin/env node
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-CON7DMNS.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};
2
+ var he=Object.defineProperty;var f=(s,e)=>()=>(s&&(e=s(s=0)),e);var D=(s,e)=>{for(var t in e)he(s,t,{get:e[t],enumerable:!0})};import{z as y}from"zod";var H,F=f(()=>{"use strict";H=y.object({environmentUrl:y.string().url("Must be a valid Dataverse environment URL").refine(s=>s.startsWith("https://"),{message:"Dataverse environment URL must use HTTPS"}).refine(s=>{try{return new URL(s).hostname.toLowerCase().endsWith(".dynamics.com")}catch{return!1}},{message:"environmentUrl must be a *.dynamics.com host"}),requestTimeoutMs:y.number().positive().default(3e4),maxRetries:y.number().min(0).max(10).default(3),authMethod:y.enum(["device-code","client-credentials","managed-identity"]).default("device-code"),tenantId:y.string().min(1).optional(),clientId:y.string().min(1).optional(),clientSecret:y.string().min(1).optional(),managedIdentityClientId:y.string().min(1).optional()}).superRefine((s,e)=>{s.authMethod==="client-credentials"&&(s.tenantId||e.addIssue({code:y.ZodIssueCode.custom,path:["tenantId"],message:"tenantId is required when authMethod is 'client-credentials'"}),s.clientId||e.addIssue({code:y.ZodIssueCode.custom,path:["clientId"],message:"clientId is required when authMethod is 'client-credentials'"}),s.clientSecret||e.addIssue({code:y.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."}))})});import{createCipheriv as pe,createDecipheriv as ge,createHash as me,randomBytes as fe}from"crypto";function B(){let s=[process.env.COMPUTERNAME??process.env.HOSTNAME??"",process.env.USERNAME??process.env.USER??"","mcp-dataverse-cache-v1"].join(".");return me("sha256").update(s).digest()}function z(s){let e=B(),t=fe(16),n=pe("aes-256-gcm",e,t),r=Buffer.concat([n.update(s,"utf-8"),n.final()]),o={v:1,iv:t.toString("hex"),tag:n.getAuthTag().toString("hex"),d:r.toString("hex")};return JSON.stringify(o)}function $(s){let e=JSON.parse(s);if(e.v!==1)throw new Error("Unknown encrypted-blob format version");let t=Buffer.from(e.iv,"hex"),n=Buffer.from(e.tag,"hex"),r=Buffer.from(e.d,"hex"),o=ge("aes-256-gcm",B(),t);return o.setAuthTag(n),o.update(r).toString("utf-8")+o.final("utf-8")}function V(s){try{let e=JSON.parse(s);return e.v===1&&typeof e.iv=="string"&&typeof e.tag=="string"&&typeof e.d=="string"}catch{return!1}}var O=f(()=>{"use strict"});var T={};D(T,{loadConfig:()=>ve});import{readFileSync as ye,existsSync as J}from"fs";import{join as K}from"path";import{homedir as we}from"os";function ve(){let s=K(we(),".mcp-dataverse",X),e=process.env.MCP_CONFIG_PATH??(J(s)?s:K(process.cwd(),X)),t={};if(J(e)){let h=ye(e,"utf-8");try{t=JSON.parse(h)}catch{throw new Error(`Invalid JSON in ${e}. Check for syntax errors (trailing commas, missing quotes).`)}}let n=process.env.DATAVERSE_ENV_URL??process.env.environmentUrl;n&&(t.environmentUrl=n);let r=process.env.REQUEST_TIMEOUT_MS??process.env.requestTimeoutMs;r&&(t.requestTimeoutMs=Number(r));let o=process.env.MAX_RETRIES??process.env.maxRetries;o&&(t.maxRetries=Number(o));let i=process.env.AUTH_METHOD??process.env.authMethod;i&&(t.authMethod=i);let a=process.env.AZURE_TENANT_ID??process.env.tenantId;a&&(t.tenantId=a);let u=process.env.AZURE_CLIENT_ID??process.env.clientId;u&&(t.clientId=u);let d=process.env.AZURE_CLIENT_SECRET??process.env.clientSecret;if(d)t.clientSecret=d;else if(typeof t.clientSecret=="string"&&V(t.clientSecret))try{t.clientSecret=$(t.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 c=process.env.AZURE_MANAGED_IDENTITY_CLIENT_ID??process.env.managedIdentityClientId;c&&(t.managedIdentityClientId=c);let l=H.safeParse(t);if(!l.success)throw new Error(`Invalid configuration:
3
+ ${l.error.issues.map(h=>` - ${h.path.join(".")}: ${h.message}`).join(`
4
+ `)}`);return l.data}var X,b=f(()=>{"use strict";F();O();X="config.json"});import{PublicClientApplication as Re}from"@azure/msal-node";import{existsSync as $e,mkdirSync as Te,readFileSync as be,writeFileSync as ke}from"fs";import{homedir as Ae}from"os";import{join as Y}from"path";import{exec as Pe}from"child_process";import{platform as G}from"process";function Z(s){let e=G==="win32"?`echo|set /p="${s}"| clip`:G==="darwin"?`printf '%s' '${s}' | pbcopy`:`printf '%s' '${s}' | xclip -selection clipboard 2>/dev/null || printf '%s' '${s}' | xsel --clipboard 2>/dev/null`;Pe(e,()=>{})}function Ie(){return{beforeCacheAccess:async s=>{if($e(q))try{let e=be(q,"utf-8"),t;try{t=$(e)}catch{t=e}s.tokenCache.deserialize(t)}catch{}},afterCacheAccess:async s=>{s.cacheHasChanged&&(Te(Q,{recursive:!0}),ke(q,z(s.tokenCache.serialize()),{encoding:"utf-8",mode:384}))}}}var Ee,Q,q,Ce,k,ee=f(()=>{"use strict";O();Ee="1950a258-227b-4e31-a9cf-717495945fc2",Q=Y(Ae(),".mcp-dataverse"),q=Y(Q,"msal-cache.json"),Ce=300*1e3;k=class{environmentUrl;pca;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;backgroundAuthFlow=null;activeDeviceCode=null;constructor(e){this.environmentUrl=e.replace(/\/$/,""),this.pca=new Re({auth:{clientId:Ee,authority:"https://login.microsoftonline.com/common"},cache:{cachePlugin:Ie()}})}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.runDeviceCodeFlowBlocking()}async refreshToken(){let e=await this.pca.getAllAccounts();if(e.length>0)try{return await this.acquireSilently()}catch{process.stderr.write(`
5
+ [mcp-dataverse] Session expired \u2014 initiating re-authentication.
6
+ `)}return this.authenticateViaDeviceCode(e.length===0?"first-time":"session-expired")}async authenticateViaDeviceCode(e){if(this.backgroundAuthFlow)try{return await this.acquireSilently()}catch{}let t=null,n=new Promise(r=>{t=r});if(this.backgroundAuthFlow||(this.activeDeviceCode=null,this.backgroundAuthFlow=this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:r=>{Z(r.userCode),this.activeDeviceCode={userCode:r.userCode,verificationUri:r.verificationUri},t?.(),t=null,process.stderr.write(`
7
+ [mcp-dataverse] Sign in required
8
+
9
+ 1. Open ${r.verificationUri} in your browser
10
+ (use the browser profile linked to your Power Platform account)
11
+ 2. Paste the code: ${r.userCode} (already copied to your clipboard)
12
+ 3. Sign in with your work account
13
+
14
+ `)}}).then(r=>{r&&(this.cacheResult(r),process.stderr.write(`
15
+ [mcp-dataverse] Authenticated \u2713 Token cached.
16
+
17
+ `))}).catch(r=>{let o=r instanceof Error?r.message:String(r);process.stderr.write(`
18
+ [mcp-dataverse] Background auth failed: ${o}
19
+ `)}).finally(()=>{this.backgroundAuthFlow=null,this.activeDeviceCode=null}),await Promise.race([n,new Promise(r=>setTimeout(r,6e3))])),this.activeDeviceCode!==null){let{userCode:r,verificationUri:o}=this.activeDeviceCode,i=e==="first-time"?"First-time sign-in required to connect to Dataverse.":"Your Dataverse session has expired. Please sign in again.";throw new Error(`${i}
20
+
21
+ 1. Open this URL in your browser:
22
+ ${o}
23
+
24
+ 2. Enter the code: ${r} (already copied to your clipboard)
25
+
26
+ 3. Sign in with your organizational (work/school) account
27
+
28
+ Authentication is running in the background. Once you have signed in, retry this command.`)}throw new Error(`Authentication required but could not reach the Microsoft sign-in service.
29
+ Check your network connection and retry.
30
+
31
+ Alternatively, pre-authenticate via the terminal:
32
+ npx mcp-dataverse-auth ${this.environmentUrl}
33
+ Then restart the MCP server.`)}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 runDeviceCodeFlowBlocking(){let e=await Promise.race([this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:t=>{Z(t.userCode),process.stderr.write(`
34
+ [mcp-dataverse] Sign in required
35
+
36
+ 1. Open ${t.verificationUri} in your browser
37
+ (use the browser profile linked to your Power Platform account)
38
+ 2. Paste the code: ${t.userCode} (already copied to your clipboard)
39
+ 3. Sign in with your work account
40
+
41
+ `)}}),new Promise((t,n)=>setTimeout(()=>n(new Error("Authentication timed out after 5 minutes. Please try again.")),Ce))]);e&&(this.cacheResult(e),process.stderr.write(`
42
+ [mcp-dataverse] Authenticated \u2713 Token cached \u2014 no sign-in needed next time.
43
+
44
+ `))}cacheResult(e){this.cachedToken=e.accessToken,this.tokenExpiresAt=e.expiresOn?.getTime()??Date.now()+3300*1e3}}});import{ConfidentialClientApplication as xe}from"@azure/msal-node";var A,te=f(()=>{"use strict";A=class{environmentUrl;cca;scopes;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(e,t,n,r){this.environmentUrl=e.replace(/\/$/,""),this.scopes=[`${this.environmentUrl}/.default`],this.cca=new xe({auth:{clientId:n,authority:`https://login.microsoftonline.com/${t}`,clientSecret:r}})}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 n=t instanceof Error?t.message:String(t);throw new Error(`Client credentials token acquisition failed: ${n}
45
+ 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 Se}from"@azure/msal-node";var P,ne=f(()=>{"use strict";P=class{environmentUrl;mia;resource;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;constructor(e,t){this.environmentUrl=e.replace(/\/$/,"");let{protocol:n,hostname:r}=new URL(this.environmentUrl);this.resource=`${n}//${r}`,this.mia=new Se(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 n=t instanceof Error?t.message:String(t);throw new Error(`Managed Identity token acquisition failed: ${n}
46
+ This method only works on Azure-hosted environments (App Service, Container Apps, VMs, AKS).
47
+ 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}}});var U={};D(U,{createAuthProvider:()=>De});function De(s){switch(s.authMethod){case"client-credentials":return new A(s.environmentUrl,s.tenantId,s.clientId,s.clientSecret);case"managed-identity":return new P(s.environmentUrl,s.managedIdentityClientId);case"device-code":default:return new k(s.environmentUrl)}}var M=f(()=>{"use strict";ee();te();ne()});var w,E,N=f(()=>{"use strict";w=class extends Error{constructor(t,n,r,o,i={}){super(t);this.status=n;this.data=r;this.code=o;this.responseHeaders=i;this.name="HttpError"}},E=class{baseURL;timeoutMs;defaultHeaders;tokenProvider;constructor(e){this.baseURL=e.baseURL.endsWith("/")?e.baseURL:e.baseURL+"/",this.timeoutMs=e.timeout??3e4,this.defaultHeaders={...e.headers},this.tokenProvider=e.tokenProvider??void 0}async get(e,t){return this.request("GET",e,void 0,t)}async post(e,t,n){return this.request("POST",e,t,n)}async patch(e,t,n){return this.request("PATCH",e,t,n)}async put(e,t,n){return this.request("PUT",e,t,n)}async delete(e,t){return this.request("DELETE",e,void 0,t)}resolveUrl(e){if(!e.startsWith("http"))return this.baseURL+e;let t=new URL(e),n=new URL(this.baseURL);if(t.origin!==n.origin)throw new w(`SSRF protection: request to '${t.origin}' blocked; only '${n.origin}' is permitted`,0,void 0,"SSRF_BLOCKED");return e}async request(e,t,n,r){let o=this.resolveUrl(t),i={...this.defaultHeaders,...r?.headers};this.tokenProvider&&(i.Authorization=`Bearer ${await this.tokenProvider()}`);let a=r?.timeoutMs??this.timeoutMs,u=new AbortController,d=setTimeout(()=>u.abort(),a);try{let c={method:e,headers:i,signal:u.signal};n!==void 0&&(c.body=typeof n=="string"?n:JSON.stringify(n));let l=await fetch(o,c),h={};if(l.headers.forEach((g,v)=>{h[v]=g}),!l.ok){let g=await l.text(),v;try{v=JSON.parse(g)}catch{v=g||void 0}throw new w(`Request failed with status ${l.status}`,l.status,v,void 0,h)}let m;if(r?.responseType==="text")m=await l.text();else{let g=await l.text();m=g?JSON.parse(g):{}}return{data:m,status:l.status,headers:h}}catch(c){throw c instanceof w?c:c instanceof DOMException&&c.name==="AbortError"?new w("Request timed out",0,void 0,"ECONNABORTED"):c}finally{clearTimeout(d)}}}});function re(s){let e=s.indexOf(`\r
48
+ \r
49
+ `),t=s.indexOf(`
50
+
51
+ `);return e!==-1&&(t===-1||e<=t)?e+4:t!==-1?t+2:-1}function ie(s,e){let t=[],n=s.split(`--${e}`);for(let r of n){let o=r.trim();if(!o||o==="--")continue;let i=re(r);if(i===-1)continue;let a=r.slice(i),u=re(a);if(u===-1)continue;let c=(a.trimStart().split(/\r?\n/)[0]??"").match(/^HTTP\/\d+\.\d+\s+(\d{3})/),l=c?parseInt(c[1],10):0,h=l>=200&&l<300,m=a.slice(u).trim();if(!m){t.push(h?null:{error:"Empty response body",status:l});continue}try{let g=JSON.parse(m);t.push(h?g:{error:g,status:l})}catch{t.push({error:m,status:l})}}return t}function p(s){return s.replace(/'/g,"''")}function se(s){if(s instanceof w){let e=s.data?.error;return e?.code==="0x80060888"?new Error(`Dataverse error 0x80060888: The Web API endpoint does not exist in this organization. This means the action/function name is wrong OR the entity set name is incorrect \u2014 NOT necessarily a permissions error. Original message: ${e.message??""}. Tip: verify action names via $metadata or use dataverse_resolve_entity_name for entity names.`):e?new Error(`Dataverse error ${e.code??""}: ${e.message??"Unknown error"}`):s.code==="ECONNABORTED"?new Error("Request timed out. Check your Dataverse environment URL."):s}return s instanceof Error?s:new Error(String(s))}var _,R=f(()=>{"use strict";N();_={opportunities:"opportunityid",territories:"territoryid",categories:"categoryid",activityparties:"activitypartyid",activitymimeattachments:"activitymimeattachmentid",queues:"queueid",queueitems:"queueitemid"}});var L,C,oe=f(()=>{"use strict";N();R();L="9.2",C=class{http;authProvider;maxRetries;constructor(e,t=3,n=3e4){this.authProvider=e,this.maxRetries=t,this.http=new E({baseURL:`${e.environmentUrl}/api/data/v${L}/`,timeout:n,headers:{"OData-MaxVersion":"4.0","OData-Version":"4.0",Accept:"application/json","Content-Type":"application/json; charset=utf-8"},tokenProvider:()=>e.getToken()})}get serviceRootUrl(){return this.http.baseURL}async requestWithRetry(e,t=0){try{return await e()}catch(n){if(n instanceof w){if(n.status===401&&t===0)return this.authProvider.invalidateToken(),this.requestWithRetry(e,t+1);if([429,503,504].includes(n.status)&&t<this.maxRetries){let o=n.responseHeaders["retry-after"],i=o?parseInt(o,10)*1e3:Math.pow(2,t)*1e3;return await new Promise(a=>setTimeout(a,i)),this.requestWithRetry(e,t+1)}if(n.status===400){let o=n.data?.error?.code;if(o==="0x80071151"&&t<5){let u=Math.pow(2,t)*5e3;return await new Promise(d=>setTimeout(d,u)),this.requestWithRetry(e,t+1)}if(o==="0x80071151"&&t>=5)throw new Error(`[TRANSIENT_LOCK_EXHAUSTED] Dataverse customization lock (0x80071151) still held after ${t} retries (~155s total). A publish operation is running server-side. WAIT at least 60 seconds, then retry the operation ONCE manually. Do NOT loop-retry. Inform the user that Dataverse is processing a previous schema change.`);let i=n.data?.error?.message?.toLowerCase()??"",a=o==="0x80044181"||i.includes("publish")&&(i.includes("already")||i.includes("in progress")||i.includes("another user"));if(a&&t<4)return await new Promise(u=>setTimeout(u,3e4)),this.requestWithRetry(e,t+1);if(a&&t>=4)throw new Error(`[TRANSIENT_LOCK_EXHAUSTED] Dataverse publish lock still held after ${t} retries (~120s total). A PublishAllXml operation is in progress server-side. WAIT at least 60 seconds before retrying ONCE. Do NOT call dataverse_publish_customizations again immediately. Inform the user that Dataverse is serialising a background publish job.`)}}throw se(n)}}async whoAmI(){return this.requestWithRetry(async()=>{let e=await this.http.get("WhoAmI"),{UserId:t,BusinessUnitId:n,OrganizationId:r}=e.data,o="";try{o=(await this.http.get(`organizations(${r})?$select=name`)).data.name??""}catch{o=""}let i=this.authProvider.environmentUrl;return{UserId:t,BusinessUnitId:n,OrganizationId:r,OrganizationName:o,EnvironmentUrl:i}})}async listTables(e=!1){let r=["$select=LogicalName,SchemaName,DisplayName,EntitySetName,PrimaryIdAttribute,PrimaryNameAttribute,IsCustomEntity,IconVectorName",e?"$filter=IsCustomEntity eq true":""].filter(Boolean).join("&");return this.requestWithRetry(()=>this.http.get(`EntityDefinitions?${r}`).then(o=>o.data.value))}async getTableMetadata(e,t=!0){let n=t?"$expand=Attributes":"",r=`EntityDefinitions(LogicalName='${p(e)}')${n?"?"+n:""}`;return this.requestWithRetry(()=>this.http.get(r).then(o=>o.data))}async fetchAllPagesOData(e){let t=[],n=e;for(;n;){let r=await this.requestWithRetry(()=>this.http.get(n).then(o=>o.data));t.push(...r.value??[]),n=r["@odata.nextLink"]}return t}async getRelationships(e){let t=p(e),[n,r,o]=await Promise.all([this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${t}')/OneToManyRelationships`),this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${t}')/ManyToOneRelationships`),this.fetchAllPagesOData(`EntityDefinitions(LogicalName='${t}')/ManyToManyRelationships`)]);return[...n,...r,...o]}async query(e,t={}){let n=a=>encodeURIComponent(a).replace(/%28/g,"(").replace(/%29/g,")").replace(/%2C/g,",").replace(/%27/g,"'").replace(/%40/g,"@"),r=[];t.select?.length&&r.push(`$select=${t.select.join(",")}`),t.filter&&r.push(`$filter=${n(t.filter)}`),t.orderby&&r.push(`$orderby=${n(t.orderby)}`),t.top&&r.push(`$top=${t.top}`),t.expand&&r.push(`$expand=${t.expand}`),t.count&&r.push("$count=true"),t.apply&&r.push(`$apply=${n(t.apply)}`);let o=`${e}${r.length?"?"+r.join("&"):""}`,i=t.formattedValues?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(o,i).then(a=>a.data))}async executeFetchXml(e,t,n){let r=encodeURIComponent(t),o=n?{headers:{Prefer:'odata.include-annotations="OData.Community.Display.V1.FormattedValue"'}}:void 0;return this.requestWithRetry(()=>this.http.get(`${e}?fetchXml=${r}`,o).then(i=>i.data))}async getRecord(e,t,n,r){let o=[];n?.length&&o.push(`$select=${n.join(",")}`),r&&o.push(`$expand=${r}`);let i=o.length?`?${o.join("&")}`:"";return this.requestWithRetry(async()=>{let a=await this.http.get(`${e}(${t})${i}`,{headers:{Prefer:'odata.include-annotations="*"'}}),u=a.headers["odata-etag"]??a.data["@odata.etag"]??null;return{record:a.data,etag:u}})}async createRecord(e,t){return this.requestWithRetry(async()=>{let n=await this.http.post(e,t,{headers:{Prefer:"return=representation"}}),o=n.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1];if(o)return o;let i=n.data,a=i["@odata.id"]?.match(/\(([^)]+)\)/)?.[1];if(a)return a;let u=_[e]??e.replace(/s$/,"")+"id",d=i[u];return d||(n.headers.location?.match(/\(([^)]+)\)/)?.[1]??"")})}async createWebResource(e){return this.requestWithRetry(async()=>(await this.http.post("webresourceset",e)).headers["odata-entityid"]?.match(/\(([^)]+)\)$/)?.[1]??"")}async updateRecord(e,t,n,r){await this.requestWithRetry(()=>this.http.patch(`${e}(${t})`,n,{headers:{"If-Match":r??"*"}}))}async deleteRecord(e,t){await this.requestWithRetry(()=>this.http.delete(`${e}(${t})`))}async upsertRecord(e,t,n,r,o="upsert",i){return this.requestWithRetry(async()=>{let a=i?`${e}(${i})`:`${e}(${p(t)}='${p(n)}')`,u={Prefer:"return=representation"};o==="createOnly"&&(u["If-None-Match"]="*"),o==="updateOnly"&&(u["If-Match"]="*");try{let d=await this.http.put(a,r,{headers:u}),c=d.status===201?"created":"updated",h=d.headers["odata-entityid"]?.match(/\(([^)]+)\)/)?.[1],m=d.data,g=_[e]??e.replace(/s$/,"")+"id",v=h??m?.[g]??n;return{operation:c,id:v}}catch(d){if(d instanceof w&&d.status===412){if(o==="createOnly")throw new Error("Record already exists");if(o==="updateOnly")throw new Error("Record not found")}throw d}})}async associate(e,t,n,r,o){let i=`${this.authProvider.environmentUrl}/api/data/v${L}/${r}(${o})`;await this.requestWithRetry(()=>this.http.post(`${e}(${t})/${n}/$ref`,{"@odata.id":i}))}async disassociate(e,t,n,r,o){let i=r?`?$id=${this.authProvider.environmentUrl}/api/data/v${L}/${o??e}(${r})`:"";await this.requestWithRetry(()=>this.http.delete(`${e}(${t})/${n}/$ref${i}`))}}});var I,ae=f(()=>{"use strict";oe();R();I=class extends C{async executeAction(e,t={}){return this.requestWithRetry(()=>this.http.post(e,t).then(n=>n.data))}async executeFunction(e,t={}){let n=[],r=[],o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;Object.entries(t).forEach(([u,d])=>{if(typeof d=="object"&&d!==null){let c=`@${u}`;r.push(`${p(u)}=${c}`),n.push(`${c}=${encodeURIComponent(JSON.stringify(d))}`)}else typeof d=="string"&&o.test(d)?r.push(`${p(u)}=${d}`):typeof d=="string"?r.push(`${p(u)}='${p(d)}'`):r.push(`${p(u)}=${String(d)}`)});let i=r.length?`${e}(${r.join(",")})`:`${e}()`,a=n.length?`${i}?${n.join("&")}`:i;return this.requestWithRetry(()=>this.http.get(a).then(u=>u.data))}async search(e){let n=`${this.http.baseURL.replace(/\/api\/data\/v[\d.]+\/?$/,"")}/api/search/v2.0/query`;return this.requestWithRetry(()=>this.http.post(n,e).then(r=>r.data))}async executeBoundAction(e,t,n,r={}){return this.requestWithRetry(()=>this.http.post(`${e}(${t})/Microsoft.Dynamics.CRM.${n}`,r).then(o=>o.data))}}});var x,ce=f(()=>{"use strict";ae();R();x=class extends I{async listDependencies(e,t){return this.requestWithRetry(()=>this.http.get(`RetrieveDependenciesForDelete(ComponentType=${e},ObjectId=${t})`).then(n=>n.data.value))}async listTableDependencies(e,t){let n={0:"Workflow",1:"Dialog",2:"BusinessRule",3:"Action",4:"BusinessProcessFlow",5:"Flow"},r={0:"Draft",1:"Active",2:"Inactive"},i=(await this.requestWithRetry(()=>this.http.get(`workflows?$filter=primaryentity eq '${p(e)}' and statecode ne 2&$select=name,workflowid,statecode,category,triggeroncreate,triggerondelete,triggeronupdateattributelist`).then(c=>c.data))).value.map(c=>{let l=[];return c.triggeroncreate&&l.push("Create"),c.triggerondelete&&l.push("Delete"),c.triggeronupdateattributelist&&l.push("Update"),{componentType:n[c.category]??`Category${c.category}`,name:c.name,id:c.workflowid,state:r[c.statecode]??`State${c.statecode}`,triggerEvent:l.length?l.join(","):null,solutionName:null}}),a=t?.length?i.filter(c=>t.includes(c.componentType)):i,d=t?.some(c=>c==="Plugin"||c==="CustomAPI")?"Plugin and CustomAPI types require additional SDK message queries and are not yet implemented. Results show Workflow/BusinessRule/Flow/Action dependencies only.":null;return{tableName:e,dependencies:a,count:a.length,warning:d}}async listGlobalOptionSets(){return this.requestWithRetry(()=>this.http.get("GlobalOptionSetDefinitions").then(e=>e.data.value))}async getOptionSet(e){return this.requestWithRetry(()=>this.http.get(`GlobalOptionSetDefinitions(Name='${p(e)}')`).then(t=>t.data))}async getAttributeOptionSet(e,t){let n=["PicklistAttributeMetadata","MultiSelectPicklistAttributeMetadata","StatusAttributeMetadata","StateAttributeMetadata"];for(let r of n)try{let o=`EntityDefinitions(LogicalName='${p(e)}')/Attributes(LogicalName='${p(t)}')/Microsoft.Dynamics.CRM.${r}?$select=LogicalName,DisplayName&$expand=OptionSet`,d=((await this.requestWithRetry(()=>this.http.get(o).then(c=>c.data))).OptionSet?.Options??[]).map(c=>({label:c.Label?.UserLocalizedLabel?.Label??"",value:c.Value}));return{entityLogicalName:e,attributeLogicalName:t,attributeType:r.replace("AttributeMetadata",""),options:d}}catch{continue}throw new Error(`Attribute '${t}' on entity '${e}' is not a Picklist, MultiSelectPicklist, Status, or State attribute, or does not exist.`)}async getEntityKeys(e){return this.requestWithRetry(async()=>(await this.http.get(`EntityDefinitions(LogicalName='${p(e)}')/Keys?$select=SchemaName,LogicalName,KeyAttributes,IsCustomizable,EntityKeyIndexStatus`)).data.value.map(n=>({schemaName:n.SchemaName,logicalName:n.LogicalName,keyAttributes:n.KeyAttributes,isCustomizable:n.IsCustomizable?.Value??!1,indexStatus:n.EntityKeyIndexStatus})))}async updateEntityDefinition(e,t){await this.requestWithRetry(()=>this.http.patch(`EntityDefinitions(LogicalName='${p(e)}')`,t,{headers:{"MSCRM.MergeLabels":"true"}}))}async replaceEntityDefinition(e,t){await this.requestWithRetry(()=>this.http.put(`EntityDefinitions(LogicalName='${p(e)}')`,t,{headers:{"MSCRM.MergeLabels":"true"}}))}async createEntityDefinition(e){return this.requestWithRetry(async()=>(await this.http.post("EntityDefinitions",e)).headers["odata-entityid"]?.match(/\(([^)]+)\)$/)?.[1]??"")}async createAttribute(e,t){return this.requestWithRetry(async()=>(await this.http.post(`EntityDefinitions(LogicalName='${p(e)}')/Attributes`,t,{headers:{Prefer:"return=representation"}})).data.MetadataId??"")}async updateAttribute(e,t,n){await this.requestWithRetry(()=>this.http.put(`EntityDefinitions(LogicalName='${p(e)}')/Attributes(LogicalName='${p(t)}')`,n,{headers:{"MSCRM.MergeLabels":"true"}}))}async deleteAttribute(e,t){await this.requestWithRetry(()=>this.http.delete(`EntityDefinitions(LogicalName='${p(e)}')/Attributes(LogicalName='${p(t)}')`))}async createRelationship(e){return this.requestWithRetry(async()=>(await this.http.post("RelationshipDefinitions",e)).headers["odata-entityid"]?.match(/\(([^)]+)\)$/)?.[1]??"")}}});var S,ue=f(()=>{"use strict";ce();R();S=class extends x{async batchExecute(e,t=!1){let n=`batch_${Date.now()}`,r="";if(t){let i=`changeset_${Date.now()+1}`,a=e.filter(d=>d.method==="GET"),u=e.filter(d=>d.method!=="GET");for(let d of a)r+=`--${n}\r
52
+ `,r+=`Content-Type: application/http\r
53
+ `,r+=`Content-Transfer-Encoding: binary\r
54
+ \r
55
+ `,r+=`${d.method} ${this.http.baseURL}${d.url} HTTP/1.1\r
56
+ `,r+=`Accept: application/json\r
57
+ \r
58
+ `;if(u.length>0){r+=`--${n}\r
59
+ `,r+=`Content-Type: multipart/mixed; boundary=${i}\r
60
+ \r
61
+ `;let d=1;for(let c of u)r+=`--${i}\r
62
+ `,r+=`Content-Type: application/http\r
63
+ `,r+=`Content-Transfer-Encoding: binary\r
64
+ `,r+=`Content-ID: ${c.contentId??d++}\r
65
+ \r
66
+ `,r+=`${c.method} ${this.http.baseURL}${c.url} HTTP/1.1\r
67
+ `,r+=`Content-Type: application/json\r
68
+ \r
69
+ `,c.body&&(r+=JSON.stringify(c.body)),r+=`\r
70
+ `;r+=`--${i}--\r
71
+ `}}else e.forEach(i=>{r+=`--${n}\r
72
+ `,r+=`Content-Type: application/http\r
73
+ `,r+=`Content-Transfer-Encoding: binary\r
74
+ \r
75
+ `,r+=`${i.method} ${this.http.baseURL}${i.url} HTTP/1.1\r
76
+ `,r+=`Content-Type: application/json\r
77
+ \r
78
+ `,i.body&&(r+=JSON.stringify(i.body)),r+=`\r
79
+ `});r+=`--${n}--`;let o=await this.requestWithRetry(()=>this.http.post("$batch",r,{headers:{"Content-Type":`multipart/mixed;boundary=${n}`},responseType:"text"}));try{let a=(o.headers["content-type"]??"").match(/boundary=(?:"([^"]+)"|([^;"\s]+))/),u=a?.[1]??a?.[2];return u?ie(o.data,u):(process.stderr.write(`[batchExecute] No multipart boundary in response Content-Type; returning raw data.
80
+ `),[o.data])}catch(i){return process.stderr.write(`[batchExecute] Failed to parse multipart response; returning raw data. ${String(i)}
81
+ `),[o.data]}}}});var de={};D(de,{DataverseAdvancedClient:()=>j});function W(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}var Oe,j,le=f(()=>{"use strict";ue();R();Oe={1:"Entity",2:"Attribute",3:"Relationship",9:"OptionSet",29:"Workflow",61:"SystemForm",71:"SiteMap",90:"PluginAssembly",92:"PluginType",97:"WebResource",95:"ServiceEndpoint",79:"ConnectionRole"};j=class extends S{async executeBoundFunction(e,t,n,r={}){let o=[],i=[];Object.entries(r).forEach(([c,l])=>{if(typeof l=="object"&&l!==null){let h=`@${c}`;i.push(`${p(c)}=${h}`),o.push(`${h}=${encodeURIComponent(JSON.stringify(l))}`)}else typeof l=="string"?i.push(`${p(c)}='${p(l)}'`):i.push(`${p(c)}=${String(l)}`)});let a=i.join(","),u=`${e}(${t})/Microsoft.Dynamics.CRM.${n}(${a})`,d=o.length?`${u}?${o.join("&")}`:u;return this.requestWithRetry(()=>this.http.get(d).then(c=>c.data))}async executeUnboundFunction(e,t={}){let n=[],r=[];Object.entries(t).forEach(([u,d])=>{if(typeof d=="object"&&d!==null){let c=`@${u}`;r.push(`${p(u)}=${c}`),n.push(`${c}=${encodeURIComponent(JSON.stringify(d))}`)}else typeof d=="string"?r.push(`${p(u)}='${p(d)}'`):r.push(`${p(u)}=${String(d)}`)});let o=r.join(","),i=`${e}(${o})`,a=n.length?`${i}?${n.join("&")}`:i;return this.requestWithRetry(()=>this.http.get(a).then(u=>u.data))}async queryWithPaging(e,t={}){let n=Math.min(t.maxTotal??5e3,5e4),r=[],o=0,i={};t.select!==void 0&&(i.select=t.select),t.filter!==void 0&&(i.filter=t.filter),t.orderby!==void 0&&(i.orderby=t.orderby),t.expand!==void 0&&(i.expand=t.expand);let a=await this.query(e,i);for(r.push(...a.value),o++;a["@odata.nextLink"]&&r.length<n;){let d=a["@odata.nextLink"];a=await this.requestWithRetry(()=>this.http.get(d).then(c=>c.data)),r.push(...a.value),o++}let u=r.slice(0,n);return{records:u,totalRetrieved:u.length,pageCount:o}}async getChangedRecords(e,t,n){let r,o={};if(t===null){let h=n?.length?`?$select=${n.join(",")}`:"";r=`${e}${h}`,o.Prefer="odata.track-changes"}else{let h=n?.length?`&$select=${n.join(",")}`:"";r=`${e}?$deltatoken=${t}${h}`}let i=await this.requestWithRetry(()=>this.http.get(r,{headers:o}).then(h=>h.data)),a=i.value??[],u=[],d=[];for(let h of a)if("@removed"in h){let m=String(h["@id"]??""),g=m.match(/\(([^)]+)\)$/);d.push({id:g?g[1]:m})}else u.push(h);let c=i["@odata.deltaLink"],l=null;if(c){let h=c.match(/\$deltatoken=([^&]+)/);l=h?decodeURIComponent(h[1]):null}return{newAndModified:u,deleted:d,nextDeltaToken:l}}async getSolutionComponents(e,t,n=200){return this.requestWithRetry(async()=>{let o=(await this.http.get(`solutions?$filter=uniquename eq '${p(e)}'&$select=solutionid,uniquename,friendlyname,version&$top=1`)).data.value;if(!o.length)throw new Error(`Solution '${e}' not found`);let i=o[0],a=i.solutionid,u=`_solutionid_value eq ${a}`;t!==void 0&&(u+=` and componenttype eq ${t}`);let c=(await this.http.get(`solutioncomponents?$filter=${u}&$select=componenttype,objectid&$top=${n}&$orderby=componenttype`)).data.value.map(l=>({componentType:l.componenttype,componentTypeName:Oe[l.componenttype]??`Type${l.componenttype}`,objectId:l.objectid}));return{solutionName:i.uniquename,solutionId:a,friendlyName:i.friendlyname,version:i.version,components:c,count:c.length}})}async publishCustomizations(e){try{return await this.requestWithRetry(async()=>{if(!(e&&((e.entities?.length??0)>0||(e.webResources?.length??0)>0||(e.optionSets?.length??0)>0)))return await this.http.post("PublishAllXml",{},{timeoutMs:12e4}),{published:!0,message:"All customizations published successfully"};let n="<importexportxml>";return e.entities?.length&&(n+="<entities>"+e.entities.map(r=>`<entity>${W(r)}</entity>`).join("")+"</entities>"),e.webResources?.length&&(n+="<webresources>"+e.webResources.map(r=>`<webresource>${W(r)}</webresource>`).join("")+"</webresources>"),e.optionSets?.length&&(n+="<optionsets>"+e.optionSets.map(r=>`<optionset>${W(r)}</optionset>`).join("")+"</optionsets>"),n+="</importexportxml>",await this.http.post("PublishXml",{ParameterXml:n},{timeoutMs:12e4}),{published:!0,message:"Selected customizations published successfully"}})}catch(t){if((t instanceof Error?t.message:String(t)).includes("[TRANSIENT_LOCK_EXHAUSTED]")&&(e?.entities?.length??0)>0)return{published:!0,message:"Publish lock exhausted but a concurrent PublishAllXml has already covered these entities. Changes are live. (covered by concurrent publish)"};throw t}}async listDataverseWorkflows(e){return this.requestWithRetry(async()=>{let t=[];e.category!==void 0&&t.push(`category eq ${e.category}`),e.nameContains&&t.push(`contains(name,'${p(e.nameContains)}')`);let n=`workflows?$select=workflowid,name,description,category,statecode,statuscode,type,modifiedon&$orderby=name asc&$top=${e.top??50}`;return t.length>0&&(n+=`&$filter=${t.join(" and ")}`),(await this.http.get(n)).data.value??[]})}async getDataverseWorkflow(e){return this.requestWithRetry(()=>this.http.get(`workflows(${e})?$select=workflowid,name,description,category,statecode,statuscode,type,modifiedon`).then(t=>t.data))}async executeFetchXmlAllPages(e,t){let n=[],r=1,o;do{let i=t;r>1&&(i=i.replace(/<fetch\b([^>]*)>/i,(c,l)=>{let h=l.replace(/\s+page="[^"]*"/g,"").replace(/\s+paging-cookie="[^"]*"/g,""),m=o?` paging-cookie="${o.replace(/"/g,"&quot;").replace(/'/g,"&apos;")}"`:"";return`<fetch${h} page="${r}"${m}>`}));let a=await this.executeFetchXml(e,i),u=a.value??[];n.push(...u);let d=a["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"];if(!d||u.length===0)break;try{let l=decodeURIComponent(decodeURIComponent(String(d))).match(/pagingcookie="([^"]+)"/);o=l?l[1]:void 0}catch{o=String(d)}if(!o||(r++,r>100))break}while(!0);return n}}});async function Tt(){let s=i=>process.stdout.write(i+`
82
+ `),e=i=>s(` \u2705 ${i}`),t=i=>s(` \u274C ${i}`);s(""),s("\u{1F3E5} mcp-dataverse doctor"),s("\u2500".repeat(50)),s("");let n=!0;s("\u{1F4CB} Environment");let r=process.version;parseInt(r.slice(1).split(".")[0],10)>=20?e(`Node.js ${r}`):(t(`Node.js ${r} \u2014 requires v20+`),n=!1),s(""),s("\u2699\uFE0F Configuration");try{let{loadConfig:i}=await Promise.resolve().then(()=>(b(),T)),a=i();e(`Environment URL: ${a.environmentUrl}`),e(`Auth method: ${a.authMethod??"device-code"}`),e(`Timeout: ${a.requestTimeoutMs}ms, Retries: ${a.maxRetries}`)}catch(i){let a=i instanceof Error?i.message:String(i);t(`Configuration error: ${a}`),n=!1,s(""),s(n?"\u2705 All checks passed!":"\u274C Some checks failed."),process.exit(n?0:1)}s(""),s("\u{1F511} Authentication");try{let{loadConfig:i}=await Promise.resolve().then(()=>(b(),T)),a=i(),u=a.authMethod??"device-code";e(`Auth method: ${u}`),u==="client-credentials"?(e(`Tenant ID : ${a.tenantId}`),e(`Client ID : ${a.clientId}`),a.clientSecret&&!process.env.AZURE_CLIENT_SECRET&&!process.env.clientSecret?s(" \u26A0\uFE0F clientSecret is stored in config file \u2014 prefer AZURE_CLIENT_SECRET env var for production"):e("Client Secret: supplied via env var")):u==="managed-identity"&&(a.managedIdentityClientId?e(`Identity type: user-assigned (clientId: ${a.managedIdentityClientId})`):e("Identity type: system-assigned"));let{createAuthProvider:d}=await Promise.resolve().then(()=>(M(),U)),l=await d(a).getToken();l?(e("Token acquired successfully"),l.split(".").length===3&&e("Valid JWT token format")):(t("No token returned"),n=!1)}catch(i){let a=i instanceof Error?i.message:String(i);t(`Auth failed: ${a}`),n=!1}s(""),s("\u{1F310} Dataverse API");try{let{loadConfig:i}=await Promise.resolve().then(()=>(b(),T)),a=i(),{createAuthProvider:u}=await Promise.resolve().then(()=>(M(),U)),{DataverseAdvancedClient:d}=await Promise.resolve().then(()=>(le(),de)),c=u(a),h=await new d(c,a.maxRetries,a.requestTimeoutMs).whoAmI();e(`Organization: ${h.OrganizationName||"N/A"}`),e(`User ID: ${h.UserId||"N/A"}`),e(`Business Unit: ${h.BusinessUnitId||"N/A"}`),e(`Environment: ${h.EnvironmentUrl||a.environmentUrl}`)}catch(i){let a=i instanceof Error?i.message:String(i);t(`API call failed: ${a}`),n=!1}s(""),s("\u2500".repeat(50)),s(n?"\u2705 All checks passed! Your mcp-dataverse setup is healthy.":"\u274C Some checks failed. Review the errors above."),s(""),process.exit(n?0:1)}export{Tt as runDoctor};
@@ -1,8 +1,9 @@
1
- import{createServer as N}from"http";import{timingSafeEqual as y,createHmac as I,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=I("sha256",d).update(p).digest(),u=I("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",T=process.env.MCP_HTTP_SECRET??process.env.BEARER_TOKEN,S=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}`,...process.env.ENTRA_REQUIRED_SCOPE!==void 0?{requiredScope:process.env.ENTRA_REQUIRED_SCOPE}:{}}:null,h=null;if(s){let{createEntraJwtValidator:n}=await import("./entra-jwt-validator-DABIEBOV.js");h=n(s),process.stderr.write(`[http-server] Auth mode: entra-jwt (tenant=${s.tenantId}, audience=${s.audience})
2
- `)}else process.stderr.write(T?`[http-server] Auth mode: static bearer token
1
+ var _=Object.defineProperty;var C=(o,r)=>()=>(o&&(r=o(o=0)),r);var R=(o,r)=>{for(var a in r)_(o,a,{get:r[a],enumerable:!0})};var S={};R(S,{createEntraJwtValidator:()=>O});import{createRemoteJWKSet as P,jwtVerify as A}from"jose";function O(o){let{tenantId:r,audience:a,requiredScope:p}=o,m=`https://login.microsoftonline.com/${r}/v2.0`,h=new URL(`https://login.microsoftonline.com/${r}/discovery/v2.0/keys`),w=P(h,{cooldownDuration:3e4,cacheMaxAge:10*6e4});return async function(l){let u;try{u=(await A(l,w,{issuer:m,audience:a,algorithms:["RS256"],clockTolerance:30})).payload}catch(i){let T=i instanceof Error?i.message:String(i);try{let t=l.split(".");if(t.length===3){let e=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));process.stderr.write(`[entra-jwt] validation failed: ${T} | aud=${JSON.stringify(e.aud)} iss=${e.iss} scp="${e.scp}" ver=${e.ver}
2
+ `)}}catch{}throw new Error(`Entra JWT validation failed: ${T}`)}if(p){let i=u.scp??"";if(!i.split(" ").includes(p))throw new Error(`Required scope '${p}' not present in token scp claim ('${i}')`)}let s=u;return{oid:s.oid??"",upn:s.upn??s.preferred_username??"",name:s.name??""}}}var y=C(()=>{"use strict"});import{createServer as H}from"http";import{timingSafeEqual as $,createHmac as I,randomUUID as J}from"crypto";import{StreamableHTTPServerTransport as U}from"@modelcontextprotocol/sdk/server/streamableHttp.js";function j(o,r){try{let a=Buffer.from("mcp-token-verify-constant"),p=I("sha256",a).update(o).digest(),m=I("sha256",a).update(r).digest();return $(p,m)}catch{return!1}}async function G(o,r,a,p){let m=process.env.MCP_HTTP_JSON_RESPONSE!=="false",h=process.env.MCP_HTTP_SECRET??process.env.BEARER_TOKEN,w=process.env.MCP_HTTP_CORS_ORIGIN??"*",c=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}`,...process.env.ENTRA_REQUIRED_SCOPE!==void 0?{requiredScope:process.env.ENTRA_REQUIRED_SCOPE}:{}}:null,l=null;if(c){let{createEntraJwtValidator:t}=await Promise.resolve().then(()=>(y(),S));l=t(c),process.stderr.write(`[http-server] Auth mode: entra-jwt (tenant=${c.tenantId}, audience=${c.audience})
3
+ `)}else process.stderr.write(h?`[http-server] Auth mode: static bearer token
3
4
  `:`[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",S),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(h){if(!i){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":v}),e.end(JSON.stringify({error:"Unauthorized"}));return}try{await h(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(T&&!A(i,T)){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 E=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",E),process.on("SIGTERM",E),await new Promise(n=>{m.on("close",n)})}export{U as startHttpTransport};
5
+ `);let u=(process.env.MCP_PUBLIC_URL??`http://localhost:${r}`).replace(/\/$/,""),s=new Map,i=H(async(t,e)=>{if(e.setHeader("Access-Control-Allow-Origin",w),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, mcp-session-id, Authorization"),t.method==="OPTIONS"){e.writeHead(204),e.end();return}let v=new URL(t.url??"/",`http://localhost:${r}`);if(v.pathname==="/health"&&t.method==="GET"){e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({status:"ok",version:a,tools:p}));return}if(v.pathname==="/.well-known/oauth-protected-resource"&&t.method==="GET"){let g=c?.tenantId??"";e.writeHead(200,{"Content-Type":"application/json","Cache-Control":"max-age=3600"});let f=c?.clientId??"";e.end(JSON.stringify({resource:`${u}/mcp`,authorization_servers:g?[`https://login.microsoftonline.com/${g}/v2.0`]:[],bearer_methods_supported:["header"],scopes_supported:f?[`api://${f}/DataverseAccess`]:[],resource_documentation:"https://github.com/aileron-split/mcp-dataverse"}));return}if(v.pathname==="/mcp"){let g=t.headers.authorization??"",f=g.startsWith("Bearer ")?g.slice(7):"",N=`${u}/.well-known/oauth-protected-resource`,E=c?`Bearer resource_metadata="${N}", realm="MCP Dataverse"`:'Bearer realm="MCP Dataverse"';if(l){if(!f){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":E}),e.end(JSON.stringify({error:"Unauthorized"}));return}try{await l(f)}catch(d){let n=d instanceof Error?d.message:String(d);process.stderr.write(`[http-server] JWT rejected: ${n}
6
+ `),e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":E}),e.end(JSON.stringify({error:"Unauthorized"}));return}}else if(h&&!j(f,h)){e.writeHead(401,{"Content-Type":"application/json","WWW-Authenticate":E}),e.end(JSON.stringify({error:"Unauthorized"}));return}try{let d=t.headers["mcp-session-id"];if(d){let n=s.get(d);if(!n){e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Session not found. Please reinitialize."}));return}await n.handleRequest(t,e)}else{if(t.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 n=new U({sessionIdGenerator:()=>J(),enableJsonResponse:m});n.sessionId&&(s.set(n.sessionId,n),n.onclose=()=>{n.sessionId&&s.delete(n.sessionId)}),await o().connect(n),await n.handleRequest(t,e),n.sessionId&&(s.has(n.sessionId)||(s.set(n.sessionId,n),n.onclose=()=>{n.sessionId&&s.delete(n.sessionId)}))}}catch(d){process.stderr.write(`[http-server] Unhandled error: ${String(d)}
7
+ `),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(t=>{i.listen(r,"0.0.0.0",()=>{process.stderr.write(`MCP Dataverse HTTP server listening on http://0.0.0.0:${r}/mcp
8
+ `),t()})});let T=async()=>{process.stderr.write(`Shutting down HTTP server...
9
+ `);for(let t of s.values())await t.close();i.close(),process.exit(0)};process.on("SIGINT",T),process.on("SIGTERM",T),await new Promise(t=>{i.on("close",t)})}export{G as startHttpTransport};
package/dist/install.js CHANGED
@@ -1,18 +1,58 @@
1
1
  #!/usr/bin/env node
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};
2
+ import{createInterface as ie}from"readline/promises";import{mkdirSync as re,writeFileSync as oe}from"fs";import{homedir as se}from"os";import{join as $}from"path";import{PublicClientApplication as q}from"@azure/msal-node";import{existsSync as j,mkdirSync as B,readFileSync as z,writeFileSync as J}from"fs";import{homedir as G}from"os";import{join as T}from"path";import{exec as Z}from"child_process";import{platform as A}from"process";import{createCipheriv as O,createDecipheriv as F,createHash as L,randomBytes as H}from"crypto";function b(){let t=[process.env.COMPUTERNAME??process.env.HOSTNAME??"",process.env.USERNAME??process.env.USER??"","mcp-dataverse-cache-v1"].join(".");return L("sha256").update(t).digest()}function g(t){let n=b(),r=H(16),s=O("aes-256-gcm",n,r),i=Buffer.concat([s.update(t,"utf-8"),s.final()]),a={v:1,iv:r.toString("hex"),tag:s.getAuthTag().toString("hex"),d:i.toString("hex")};return JSON.stringify(a)}function k(t){let n=JSON.parse(t);if(n.v!==1)throw new Error("Unknown encrypted-blob format version");let r=Buffer.from(n.iv,"hex"),s=Buffer.from(n.tag,"hex"),i=Buffer.from(n.d,"hex"),a=F("aes-256-gcm",b(),r);return a.setAuthTag(s),a.update(i).toString("utf-8")+a.final("utf-8")}function P(t){let n=A==="win32"?`echo|set /p="${t}"| clip`:A==="darwin"?`printf '%s' '${t}' | pbcopy`:`printf '%s' '${t}' | xclip -selection clipboard 2>/dev/null || printf '%s' '${t}' | xsel --clipboard 2>/dev/null`;Z(n,()=>{})}var Y="1950a258-227b-4e31-a9cf-717495945fc2",E=T(G(),".mcp-dataverse"),C=T(E,"msal-cache.json"),W=300*1e3;function K(){return{beforeCacheAccess:async t=>{if(j(C))try{let n=z(C,"utf-8"),r;try{r=k(n)}catch{r=n}t.tokenCache.deserialize(r)}catch{}},afterCacheAccess:async t=>{t.cacheHasChanged&&(B(E,{recursive:!0}),J(C,g(t.tokenCache.serialize()),{encoding:"utf-8",mode:384}))}}}var y=class{environmentUrl;pca;cachedToken=null;tokenExpiresAt=0;pendingAuth=null;backgroundAuthFlow=null;activeDeviceCode=null;constructor(n){this.environmentUrl=n.replace(/\/$/,""),this.pca=new q({auth:{clientId:Y,authority:"https://login.microsoftonline.com/common"},cache:{cachePlugin:K()}})}async getToken(){let n=Date.now();return this.cachedToken!==null&&this.tokenExpiresAt>n+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.runDeviceCodeFlowBlocking()}async refreshToken(){let n=await this.pca.getAllAccounts();if(n.length>0)try{return await this.acquireSilently()}catch{process.stderr.write(`
3
+ [mcp-dataverse] Session expired \u2014 initiating re-authentication.
4
+ `)}return this.authenticateViaDeviceCode(n.length===0?"first-time":"session-expired")}async authenticateViaDeviceCode(n){if(this.backgroundAuthFlow)try{return await this.acquireSilently()}catch{}let r=null,s=new Promise(i=>{r=i});if(this.backgroundAuthFlow||(this.activeDeviceCode=null,this.backgroundAuthFlow=this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:i=>{P(i.userCode),this.activeDeviceCode={userCode:i.userCode,verificationUri:i.verificationUri},r?.(),r=null,process.stderr.write(`
5
+ [mcp-dataverse] Sign in required
6
+
7
+ 1. Open ${i.verificationUri} in your browser
8
+ (use the browser profile linked to your Power Platform account)
9
+ 2. Paste the code: ${i.userCode} (already copied to your clipboard)
10
+ 3. Sign in with your work account
11
+
12
+ `)}}).then(i=>{i&&(this.cacheResult(i),process.stderr.write(`
13
+ [mcp-dataverse] Authenticated \u2713 Token cached.
14
+
15
+ `))}).catch(i=>{let a=i instanceof Error?i.message:String(i);process.stderr.write(`
16
+ [mcp-dataverse] Background auth failed: ${a}
17
+ `)}).finally(()=>{this.backgroundAuthFlow=null,this.activeDeviceCode=null}),await Promise.race([s,new Promise(i=>setTimeout(i,6e3))])),this.activeDeviceCode!==null){let{userCode:i,verificationUri:a}=this.activeDeviceCode,l=n==="first-time"?"First-time sign-in required to connect to Dataverse.":"Your Dataverse session has expired. Please sign in again.";throw new Error(`${l}
18
+
19
+ 1. Open this URL in your browser:
20
+ ${a}
21
+
22
+ 2. Enter the code: ${i} (already copied to your clipboard)
23
+
24
+ 3. Sign in with your organizational (work/school) account
25
+
26
+ Authentication is running in the background. Once you have signed in, retry this command.`)}throw new Error(`Authentication required but could not reach the Microsoft sign-in service.
27
+ Check your network connection and retry.
28
+
29
+ Alternatively, pre-authenticate via the terminal:
30
+ npx mcp-dataverse-auth ${this.environmentUrl}
31
+ Then restart the MCP server.`)}async acquireSilently(){let n=await this.pca.getAllAccounts();if(n.length===0)throw new Error("No account found in cache after authentication.");let r=await this.pca.acquireTokenSilent({scopes:[`${this.environmentUrl}/.default`],account:n[0]});if(!r?.accessToken)throw new Error("Token acquisition returned an empty access token.");return this.cacheResult(r),r.accessToken}async runDeviceCodeFlowBlocking(){let n=await Promise.race([this.pca.acquireTokenByDeviceCode({scopes:[`${this.environmentUrl}/.default`],deviceCodeCallback:r=>{P(r.userCode),process.stderr.write(`
32
+ [mcp-dataverse] Sign in required
33
+
34
+ 1. Open ${r.verificationUri} in your browser
35
+ (use the browser profile linked to your Power Platform account)
36
+ 2. Paste the code: ${r.userCode} (already copied to your clipboard)
37
+ 3. Sign in with your work account
38
+
39
+ `)}}),new Promise((r,s)=>setTimeout(()=>s(new Error("Authentication timed out after 5 minutes. Please try again.")),W))]);n&&(this.cacheResult(n),process.stderr.write(`
40
+ [mcp-dataverse] Authenticated \u2713 Token cached \u2014 no sign-in needed next time.
41
+
42
+ `))}cacheResult(n){this.cachedToken=n.accessToken,this.tokenExpiresAt=n.expiresOn?.getTime()??Date.now()+3300*1e3}};import{execFileSync as D}from"child_process";import{existsSync as R,readFileSync as Q,readdirSync as X,writeFileSync as ee}from"fs";import{homedir as te}from"os";import{join as h}from"path";var e=t=>{process.stdout.write(t+`
43
+ `)},d=()=>{process.stdout.write("\u2500".repeat(58)+`
44
+ `)};function U(t){let n=JSON.stringify({name:"mcp-dataverse",type:"stdio",command:"npx",args:["-y","mcp-dataverse"],env:{MCP_CONFIG_PATH:t}}),r=[{label:"VS Code Insiders",bin:"code-insiders"},{label:"VS Code Stable",bin:"code"}],s=[];for(let{label:i,bin:a}of r)try{let l=process.platform==="win32"?["/c",a,"--add-mcp",n]:["--add-mcp",n],u=process.platform==="win32"?"cmd.exe":a;D(u,l,{stdio:"pipe",timeout:15e3}),s.push({label:i,ok:!0})}catch{s.push({label:i,ok:!1})}return s}function I(t){let n=JSON.stringify({name:"mcp-dataverse",type:"http",url:t}),r=[{label:"VS Code Insiders",bin:"code-insiders"},{label:"VS Code Stable",bin:"code"}],s=[];for(let{label:i,bin:a}of r)try{let l=process.platform==="win32"?["/c",a,"--add-mcp",n]:["--add-mcp",n],u=process.platform==="win32"?"cmd.exe":a;D(u,l,{stdio:"pipe",timeout:15e3}),s.push({label:i,ok:!0})}catch{s.push({label:i,ok:!1})}return s}function ne(){let t=te();switch(process.platform){case"win32":{let n=process.env.APPDATA??h(t,"AppData","Roaming");return[{label:"VS Code Insiders",dir:h(n,"Code - Insiders","User")},{label:"VS Code Stable",dir:h(n,"Code","User")}]}case"darwin":return[{label:"VS Code Insiders",dir:h(t,"Library","Application Support","Code - Insiders","User")},{label:"VS Code Stable",dir:h(t,"Library","Application Support","Code","User")}];default:return[{label:"VS Code Insiders",dir:h(t,".config","Code - Insiders","User")},{label:"VS Code Stable",dir:h(t,".config","Code","User")}]}}function w(t){let n=[];for(let{dir:r}of ne()){let s=h(r,"profiles");if(!R(s))continue;let i;try{i=X(s)}catch{continue}for(let a of i){let l=h(s,a,"mcp.json");if(R(l))try{let u=Q(l,"utf-8"),f=JSON.parse(u);f.servers||(f.servers={}),f.servers["mcp-dataverse"]=t,ee(l,JSON.stringify(f,null," ")+`
45
+ `,"utf-8"),n.push(l)}catch{}}}return n}function x(t){if(!t.startsWith("https://"))return"URL must start with https://";try{if(!new URL(t).hostname.toLowerCase().endsWith(".dynamics.com"))return"Must be a *.dynamics.com URL (your Power Platform environment)"}catch{return"Invalid URL format"}return null}function M(t){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": "${t}"`),e(" }")}function V(t){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": "${t.replace(/\\/g,"\\\\")}" }`),e(" }")}var _=$(se(),".mcp-dataverse"),v=$(_,"config.json");async function Ue(){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 t=ie({input:process.stdin,output:process.stdout});e("Step 1 / 2 \u2014 Connection Mode"),d(),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 n="http";for(;;){let o=(await t.question("Select [1/2] default 1: ")).trim();if(!o||o==="1"){n="http";break}if(o==="2"){n="stdio";break}e(` \u2717 Please enter 1 or 2.
46
+ `)}if(e(` \u2713 ${n==="http"?"HTTP (hosted)":"stdio (local)"}`),e(""),n==="http"){e("Step 2 / 2 \u2014 MCP Server Endpoint"),d(),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 o="";for(;!o;){let p=(await t.question(`Server URL
47
+ \u203A `)).trim().replace(/\/$/,"");if(!p.startsWith("https://")){e(` \u2717 URL must start with https://
48
+ `);continue}try{new URL(p)}catch{e(` \u2717 Invalid URL format.
49
+ `);continue}o=p}e(` \u2713 ${o}`),e(""),t.close(),e("Registering in VS Code\u2026");let c=I(o),m=c.filter(p=>p.ok);if(m.length>0){for(let{label:p}of m)e(` \u2713 ${p}`);for(let{label:p}of c.filter(N=>!N.ok))e(` \u2013 ${p}: not found on PATH (skipped)`)}else e(" \u26A0 Neither 'code' nor 'code-insiders' found on PATH."),e(" Register manually:"),e(""),M(o);let S=w({type:"http",url:o});S.length>0&&e(` + Patched ${S.length} custom VS Code profile(s)`),d(),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"),d(),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 r="";for(;!r;){let o=(await t.question(`Environment URL (e.g. https://contoso.crm.dynamics.com)
50
+ \u203A `)).trim().replace(/\/$/,""),c=x(o);if(c){e(` \u2717 ${c}
51
+ `);continue}r=o}e(` \u2713 ${r}`),e(""),e("Step 3 / 3 \u2014 Authentication Method"),d(),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 s="device-code";for(;;){let o=(await t.question("Select [1/2] default 1: ")).trim();if(!o||o==="1"){s="device-code";break}if(o==="2"){s="client-credentials";break}e(` \u2717 Please enter 1 or 2.
52
+ `)}e(` \u2713 ${s}`),e("");let i={environmentUrl:r,authMethod:s};if(s==="client-credentials"){e("Step 4 / 4 \u2014 App Registration Details"),d(),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 o="";for(;!o;)o=(await t.question(`Tenant ID (env: AZURE_TENANT_ID)
53
+ \u203A `)).trim(),o||e(` \u2717 Required.
54
+ `);let c="";for(;!c;)c=(await t.question(`Client ID (env: AZURE_CLIENT_ID)
55
+ \u203A `)).trim(),c||e(` \u2717 Required.
56
+ `);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 m=(await t.question(`Client Secret (env: AZURE_CLIENT_SECRET) [Enter to skip]
57
+ \u203A `)).trim();i.tenantId=o,i.clientId=c,m?(i.clientSecret=g(m),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("")}t.close(),e("Saving configuration\u2026"),re(_,{recursive:!0}),oe(v,JSON.stringify(i,null,2)+`
58
+ `,{encoding:"utf-8",mode:384}),e(` \u2713 ${v}`),e(""),e("Registering in VS Code\u2026");let a={type:"stdio",command:"npx",args:["-y","mcp-dataverse"],env:{MCP_CONFIG_PATH:v}},l=U(v),u=l.filter(o=>o.ok);if(u.length>0){for(let{label:o}of u)e(` \u2713 ${o}`);for(let{label:o}of l.filter(c=>!c.ok))e(` \u2013 ${o}: not found on PATH (skipped)`)}else e(" \u26A0 Neither 'code' nor 'code-insiders' found on PATH."),e(" Register manually:"),e(""),V(v);let f=w(a);if(f.length>0&&e(` + Patched ${f.length} custom VS Code profile(s)`),e(""),d(),s!=="device-code"){e(""),e(" \u2713 Setup complete!"),e(""),e(" Restart VS Code to activate the MCP server."),s==="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(r).setupViaDeviceCode(),d(),e(""),e(" \u2713 Setup complete!"),e(""),u.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(o){let c=o instanceof Error?o.message:String(o);d(),e(` \u2717 Authentication failed: ${c}`),e(""),e(" Configuration was saved. Run 'npx mcp-dataverse install' again to retry."),e(""),process.exit(1)}}export{Ue as runInstall};