powerautomate-mcp 0.7.2 → 0.7.5

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 CHANGED
@@ -20,13 +20,16 @@ An MCP (Model Context Protocol) server that connects Claude to Microsoft Power A
20
20
  - Microsoft 365 work account with Power Automate access
21
21
  - **Linux only**: libsecret for secure token storage
22
22
  ```bash
23
- # Ubuntu/Debian
24
- sudo apt-get install libsecret-1-dev gnome-keyring
23
+ # Ubuntu/Debian runtime
24
+ sudo apt-get install libsecret-1-0 gnome-keyring
25
25
 
26
- # Fedora/RHEL
27
- sudo dnf install libsecret-devel gnome-keyring
26
+ # Fedora/RHEL runtime
27
+ sudo dnf install libsecret gnome-keyring
28
28
  ```
29
29
 
30
+ If setup fails with `libsecret-1.so.0: cannot open shared object file`, the
31
+ runtime package above is missing.
32
+
30
33
  ### Installation
31
34
 
32
35
  ```bash
@@ -215,7 +218,7 @@ npm run build
215
218
 
216
219
  This server implements defense-in-depth security:
217
220
 
218
- - **Secure Token Storage**: DPAPI (Windows), Keychain (macOS), libsecret (Linux) no plaintext fallback
221
+ - **Secure Token Storage**: DPAPI (Windows), Keychain (macOS), libsecret on Linux when available, with a 0o600 file-cache fallback when it is not
219
222
  - **Input Validation**: GUID validation on all IDs, OData injection protection (ASCII-only), path traversal blocking (including URL-encoded), SharePoint hostname allowlist
220
223
  - **SSRF Prevention**: Domain allowlists on Dataverse URLs, resource links, and token resources
221
224
  - **Injection Prevention**: Power Automate expression injection blocking (`@{`/`}@`), OData filter sanitization, command injection prevention (`execFile` over `exec`)
package/dist/index.js CHANGED
@@ -13,7 +13,6 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
13
13
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
14
14
  import { PublicClientApplication, LogLevel } from '@azure/msal-node';
15
15
  import { execFile } from 'child_process';
16
- import { PersistenceCreator, DataProtectionScope, PersistenceCachePlugin } from '@azure/msal-node-extensions';
17
16
  import { request } from 'undici';
18
17
  import Database from 'better-sqlite3';
19
18
  import { fileURLToPath } from 'url';
@@ -725,6 +724,23 @@ function validateEnvironmentId(envId) {
725
724
  }
726
725
  return envId;
727
726
  }
727
+ var msalNodeExtensionsPromise = null;
728
+ async function loadMsalNodeExtensions() {
729
+ if (!msalNodeExtensionsPromise) {
730
+ msalNodeExtensionsPromise = import('@azure/msal-node-extensions').then((mod) => {
731
+ const moduleWithOptionalDefault = mod;
732
+ const resolved = moduleWithOptionalDefault.default ?? moduleWithOptionalDefault;
733
+ if (!resolved || !("PersistenceCreator" in resolved)) {
734
+ throw new Error("msal-node-extensions did not expose the expected exports");
735
+ }
736
+ return resolved;
737
+ });
738
+ }
739
+ return msalNodeExtensionsPromise;
740
+ }
741
+ function formatError(error) {
742
+ return error instanceof Error ? error.message : String(error);
743
+ }
728
744
  var NativeFileCachePlugin = class {
729
745
  cachePath;
730
746
  constructor(cachePath) {
@@ -765,6 +781,11 @@ async function createPersistentCachePlugin(cachePath) {
765
781
  logger.debug({ cacheLocation, platform: platform }, "Creating persistent token cache");
766
782
  if (platform === "win32" || platform === "darwin") {
767
783
  try {
784
+ const {
785
+ DataProtectionScope,
786
+ PersistenceCreator,
787
+ PersistenceCachePlugin
788
+ } = await loadMsalNodeExtensions();
768
789
  const persistence = await PersistenceCreator.createPersistence({
769
790
  cachePath: cacheLocation,
770
791
  dataProtectionScope: platform === "win32" ? DataProtectionScope.CurrentUser : void 0,
@@ -777,11 +798,15 @@ async function createPersistentCachePlugin(cachePath) {
777
798
  } catch (err) {
778
799
  logger.error({ err, cacheLocation }, "Failed to create persistent cache");
779
800
  throw new Error(
780
- `Failed to initialize token cache at ${cacheLocation}: ${err instanceof Error ? err.message : String(err)}`
801
+ `Failed to initialize token cache at ${cacheLocation}: ${formatError(err)}`
781
802
  );
782
803
  }
783
804
  }
784
805
  try {
806
+ const {
807
+ PersistenceCreator,
808
+ PersistenceCachePlugin
809
+ } = await loadMsalNodeExtensions();
785
810
  const persistence = await PersistenceCreator.createPersistence({
786
811
  cachePath: cacheLocation,
787
812
  serviceName: "powerautomate-mcp",
@@ -793,7 +818,7 @@ async function createPersistentCachePlugin(cachePath) {
793
818
  return new PersistenceCachePlugin(persistence);
794
819
  } catch (libsecretError) {
795
820
  logger.warn(
796
- { err: libsecretError instanceof Error ? libsecretError.message : String(libsecretError) },
821
+ { err: formatError(libsecretError) },
797
822
  "libsecret unavailable \u2014 using native file cache fallback (0o600 permissions)"
798
823
  );
799
824
  logger.info({ cacheLocation }, "Persistent token cache initialized (native file)");
@@ -1041,6 +1066,19 @@ async function createWamAuthProvider(config, silentOnly = false) {
1041
1066
  const result = await acquireToken(SCOPES.FLOW);
1042
1067
  return result.accessToken;
1043
1068
  },
1069
+ async tryGetTokenForResourceSilent(resource) {
1070
+ try {
1071
+ validateHttpsUrl(resource, "Token resource");
1072
+ const url = new URL(resource);
1073
+ if (!isAllowedTokenDomain(url.hostname)) return null;
1074
+ if (!currentAccount) return null;
1075
+ const scopes = [`${resource}/.default`];
1076
+ const result = await pca.acquireTokenSilent({ scopes, account: currentAccount });
1077
+ return result.accessToken;
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ },
1044
1082
  async getTokenForResource(resource) {
1045
1083
  validateHttpsUrl(resource, "Token resource");
1046
1084
  const url = new URL(resource);
@@ -14857,6 +14895,26 @@ async function createDeviceCodeAuthProvider(config, silentOnly = false) {
14857
14895
  const result = await acquireToken(scopes);
14858
14896
  return result.accessToken;
14859
14897
  },
14898
+ async tryGetTokenForResourceSilent(resource) {
14899
+ try {
14900
+ validateHttpsUrl(resource, "Token resource");
14901
+ const url = new URL(resource);
14902
+ if (!isAllowedTokenDomain(url.hostname)) {
14903
+ return null;
14904
+ }
14905
+ if (!currentAccount) {
14906
+ return null;
14907
+ }
14908
+ const scopes = [`${resource}/.default`];
14909
+ const result = await pca.acquireTokenSilent({
14910
+ scopes,
14911
+ account: currentAccount
14912
+ });
14913
+ return result.accessToken;
14914
+ } catch {
14915
+ return null;
14916
+ }
14917
+ },
14860
14918
  async logout() {
14861
14919
  if (currentAccount) {
14862
14920
  const cache = pca.getTokenCache();
@@ -14952,18 +15010,21 @@ var REQUIRED_RESOURCE_ACCESS = [
14952
15010
  ]
14953
15011
  },
14954
15012
  {
14955
- resourceAppId: "00000007-0000-0000-c000-000000000000",
15013
+ resourceAppId: "475226c6-020e-4fb2-8a90-7a972cbfc1d4",
14956
15014
  resourceAccess: [
14957
- { id: "78ce3f0f-a1ce-49c2-8cde-64b5c0896db4", type: "Scope" }
15015
+ { id: "a4c50e78-6978-4ce3-b1c0-965c1f9b1128", type: "Scope" }
14958
15016
  ]
14959
15017
  },
14960
15018
  {
14961
- // Business Application Platform (BAP) API — required for environment
14962
- // discovery and Power Platform admin operations
14963
15019
  resourceAppId: "0e0bf3cc-3078-4fd4-9ef3-cb6dc0245b10",
14964
15020
  resourceAccess: [
14965
15021
  { id: "4ae1bf56-f562-4747-b7bc-2fa0874ed46f", type: "Scope" }
14966
- // user_impersonation
15022
+ ]
15023
+ },
15024
+ {
15025
+ resourceAppId: "00000007-0000-0000-c000-000000000000",
15026
+ resourceAccess: [
15027
+ { id: "78ce3f0f-a1ce-49c2-8cde-64b5c0896db4", type: "Scope" }
14967
15028
  ]
14968
15029
  }
14969
15030
  ];
@@ -15098,6 +15159,7 @@ var c = {
15098
15159
  var icons = {
15099
15160
  check: `${c.green}\u2713${c.reset}`,
15100
15161
  cross: `${c.red}\u2717${c.reset}`,
15162
+ info: `${c.blue}i${c.reset}`,
15101
15163
  arrow: `${c.cyan}\u2192${c.reset}`,
15102
15164
  bullet: `${c.dim}\u2022${c.reset}`,
15103
15165
  star: `${c.yellow}\u2605${c.reset}`
@@ -15266,15 +15328,15 @@ async function runSetupWizard() {
15266
15328
  print(` ${icons.check} ${c.green}Signed in${c.reset}`);
15267
15329
  }
15268
15330
  const additionalResources = [
15269
- "https://api.bap.microsoft.com",
15270
- "https://service.powerapps.com"
15331
+ { url: "https://service.powerapps.com", label: "PowerApps (connections)" },
15332
+ { url: "https://api.bap.microsoft.com", label: "Admin API (environments)" }
15271
15333
  ];
15272
- for (const resource of additionalResources) {
15273
- try {
15274
- await authProvider.getTokenForResource(resource);
15275
- print(` ${icons.check} ${c.dim}Authorized ${resource.replace("https://", "")}${c.reset}`);
15276
- } catch {
15277
- print(` ${c.dim}${icons.warn} Could not authorize ${resource.replace("https://", "")} (admin tools may be limited)${c.reset}`);
15334
+ for (const { url, label } of additionalResources) {
15335
+ const token = authProvider.tryGetTokenForResourceSilent ? await authProvider.tryGetTokenForResourceSilent(url) : null;
15336
+ if (token) {
15337
+ print(` ${icons.check} ${c.dim}${label}: authorized${c.reset}`);
15338
+ } else {
15339
+ print(` ${c.dim}${icons.info || "-"} ${label}: will be authorized via admin consent (Step 3)${c.reset}`);
15278
15340
  }
15279
15341
  }
15280
15342
  } catch (error) {