power-automate-mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/auth/token-cache.d.ts +7 -0
- package/dist/auth/token-cache.js +2 -0
- package/dist/auth/token-cache.js.map +1 -0
- package/dist/auth/token-manager.d.ts +2 -0
- package/dist/auth/token-manager.js +2 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/auth/types.d.ts +2 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/backend/flow-api/client.d.ts +3 -0
- package/dist/backend/flow-api/client.js +2 -0
- package/dist/backend/flow-api/client.js.map +1 -0
- package/dist/backend/flow-api/connections.d.ts +10 -0
- package/dist/backend/flow-api/connections.js +2 -0
- package/dist/backend/flow-api/connections.js.map +1 -0
- package/dist/backend/flow-api/environments.d.ts +10 -0
- package/dist/backend/flow-api/environments.js +2 -0
- package/dist/backend/flow-api/environments.js.map +1 -0
- package/dist/backend/flow-api/flows.d.ts +2 -0
- package/dist/backend/flow-api/flows.js +2 -0
- package/dist/backend/flow-api/flows.js.map +1 -0
- package/dist/backend/flow-api/owners.d.ts +2 -0
- package/dist/backend/flow-api/owners.js +2 -0
- package/dist/backend/flow-api/owners.js.map +1 -0
- package/dist/backend/flow-api/runs.d.ts +2 -0
- package/dist/backend/flow-api/runs.js +2 -0
- package/dist/backend/flow-api/runs.js.map +1 -0
- package/dist/backend/index.d.ts +31 -0
- package/dist/backend/index.js +2 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/types.d.ts +2 -0
- package/dist/backend/types.js +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +3 -0
- package/dist/bin.js.map +1 -0
- package/dist/client-DCWt6ssY.d.ts +20 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/errors-BsWrc7px.d.ts +42 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/flows-B0jBc1wf.d.ts +16 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/owners-CdI2c9rS.d.ts +18 -0
- package/dist/runs-DdpaFLpn.d.ts +17 -0
- package/dist/token-manager-BfUM20Tf.d.ts +15 -0
- package/dist/tools/connections.d.ts +9 -0
- package/dist/tools/connections.js +3 -0
- package/dist/tools/connections.js.map +1 -0
- package/dist/tools/environments.d.ts +8 -0
- package/dist/tools/environments.js +3 -0
- package/dist/tools/environments.js.map +1 -0
- package/dist/tools/flows.d.ts +10 -0
- package/dist/tools/flows.js +6 -0
- package/dist/tools/flows.js.map +1 -0
- package/dist/tools/owners.d.ts +10 -0
- package/dist/tools/owners.js +5 -0
- package/dist/tools/owners.js.map +1 -0
- package/dist/tools/runs.d.ts +10 -0
- package/dist/tools/runs.js +6 -0
- package/dist/tools/runs.js.map +1 -0
- package/dist/tools/shared.d.ts +29 -0
- package/dist/tools/shared.js +2 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/types-DRNRhdJl.d.ts +62 -0
- package/dist/types-HGbUI9Dc.d.ts +41 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Jordan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# power-automate-mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sapientsai/power-automate-mcp-server/actions/workflows/node.js.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/power-automate-mcp-server)
|
|
5
|
+
[](https://www.npmjs.com/package/power-automate-mcp-server)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
An MCP server that lets agents **inspect and operate Microsoft Power Automate cloud flows**
|
|
9
|
+
from a CLI/agent context — list and inspect flows, debug runs, check connections and owners,
|
|
10
|
+
and (when explicitly enabled) enable/disable flows, cancel/resubmit runs, and manage owners.
|
|
11
|
+
|
|
12
|
+
The Power Automate **portal** is the authoring surface; this server is the **management**
|
|
13
|
+
surface. Built on [SomaMCP](https://github.com/jordanburke/somamcp) (telemetry, health/info/
|
|
14
|
+
dashboard, error classification) over FastMCP.
|
|
15
|
+
|
|
16
|
+
> ⚠️ **Unofficial API.** v1 targets `api.flow.microsoft.com` — the surface the Power Automate
|
|
17
|
+
> portal itself uses. Microsoft labels it _"isn't supported. Customers should instead use the
|
|
18
|
+
> Dataverse Web APIs."_ It is stable in practice and, unlike Dataverse, sees **all** flows
|
|
19
|
+
> (including personal "My Flows") and works on M365‑seeded entitlements (no Premium license).
|
|
20
|
+
> Every tool's description carries this disclaimer. A supported Dataverse backend is stubbed
|
|
21
|
+
> for the future (see [`src/backend/dataverse/README.md`](src/backend/dataverse/README.md)).
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm install
|
|
27
|
+
cp .env.example .env # set AZURE_CLIENT_ID (see "App registration" below)
|
|
28
|
+
pnpm build
|
|
29
|
+
pnpm dev:stdio # local agent over stdio (device-code sign-in to stderr)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
On first use the server prints a device-code prompt to **stderr**; open
|
|
33
|
+
`https://microsoft.com/devicelogin`, enter the code, and sign in. The token is cached
|
|
34
|
+
(`TOKEN_CACHE_PATH`, mode 0600) and silently refreshed thereafter.
|
|
35
|
+
|
|
36
|
+
### Add to an MCP client (stdio)
|
|
37
|
+
|
|
38
|
+
```jsonc
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"power-automate": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "power-automate-mcp-server", "--stdio"],
|
|
44
|
+
"env": { "AZURE_CLIENT_ID": "<your-app-registration-client-id>" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## App registration
|
|
51
|
+
|
|
52
|
+
This server ships **no** default client id — you register your own (one‑time):
|
|
53
|
+
|
|
54
|
+
1. **Azure Portal → Microsoft Entra ID → App registrations → New registration.**
|
|
55
|
+
2. Name it (e.g. `power-automate-mcp`). Supported account types: **multitenant** (or
|
|
56
|
+
single‑tenant if you'll only ever use one org).
|
|
57
|
+
3. **Authentication → Add a platform → Mobile and desktop applications.** Add redirect URI
|
|
58
|
+
`http://localhost` (unused by device code, but required to register the platform). Set
|
|
59
|
+
**"Allow public client flows" = Yes**.
|
|
60
|
+
4. **API permissions → Add a permission.** You need a **delegated** permission for the Power
|
|
61
|
+
Automate / _Microsoft Flow Service_ API. If it isn't in the picker, see "Token audience"
|
|
62
|
+
below — this is the known friction point.
|
|
63
|
+
5. Copy the **Application (client) ID** → `AZURE_CLIENT_ID`.
|
|
64
|
+
|
|
65
|
+
For unattended `clientCredentials` mode instead: add a **client secret**, grant **application**
|
|
66
|
+
permissions with **admin consent**, and set `AZURE_AUTH_MODE=clientCredentials`,
|
|
67
|
+
`AZURE_TENANT_ID=<your tenant>`, `AZURE_CLIENT_SECRET=...`. Note app‑only has **limited Flow
|
|
68
|
+
reach** (it generally cannot see personal "My Flows").
|
|
69
|
+
|
|
70
|
+
### Token audience — verify on first run ⚠️
|
|
71
|
+
|
|
72
|
+
The exact OAuth scope that mints a token for the `service.flow.microsoft.com` audience from a
|
|
73
|
+
_custom_ public client is **the #1 thing to confirm**. The server tries these in order at
|
|
74
|
+
first sign‑in and logs the winner to stderr/telemetry:
|
|
75
|
+
|
|
76
|
+
1. `https://service.flow.microsoft.com//.default` (double slash is intentional in some samples)
|
|
77
|
+
2. `https://service.flow.microsoft.com/.default`
|
|
78
|
+
3. `https://service.flow.microsoft.com/User`
|
|
79
|
+
|
|
80
|
+
Pin the winner via `FLOW_SCOPES` and record it in [`docs/api-notes.md`](docs/api-notes.md).
|
|
81
|
+
If none work, the _Microsoft Flow Service_ delegated permission likely isn't grantable to a
|
|
82
|
+
third‑party app in your tenant — a tenant admin must add it (see `docs/api-notes.md` Plan B/C).
|
|
83
|
+
|
|
84
|
+
## Tools
|
|
85
|
+
|
|
86
|
+
All tools are **read‑only by default**. Write tools are registered but **refuse** unless
|
|
87
|
+
`ENABLE_WRITE_OPS=true`.
|
|
88
|
+
|
|
89
|
+
### Read-only (always enabled)
|
|
90
|
+
|
|
91
|
+
| Tool | Parameters | Returns |
|
|
92
|
+
| ------------------- | ------------------------------------------------ | -------------------------------------------------------------------------- |
|
|
93
|
+
| `list_environments` | — | `{ id, name, displayName, location, isDefault }[]` |
|
|
94
|
+
| `list_flows` | `environment?`, `owner?` | `{ name, displayName, state, createdTime, lastModifiedTime, owner }[]` |
|
|
95
|
+
| `get_flow` | `environment?`, `flow` | full flow incl. `definition`, `connectionReferences`, trigger/action names |
|
|
96
|
+
| `list_flow_runs` | `environment?`, `flow`, `top?` (≤100), `status?` | `{ name, status, startTime, endTime, durationMs, triggerName, error }[]` |
|
|
97
|
+
| `get_flow_run` | `environment?`, `flow`, `run` | run detail + first‑failure + `raw` properties (debugging) |
|
|
98
|
+
| `list_connections` | `environment?` | `{ name, apiName, displayName, status, accountName, expiresAt }[]` |
|
|
99
|
+
| `list_flow_owners` | `environment?`, `flow` | `{ principalId, principalType, roleName, principalDisplayName }[]` |
|
|
100
|
+
|
|
101
|
+
### Write (require `ENABLE_WRITE_OPS=true`)
|
|
102
|
+
|
|
103
|
+
| Tool | Parameters |
|
|
104
|
+
| ------------------------------ | ------------------------------------------------------------------------ |
|
|
105
|
+
| `enable_flow` / `disable_flow` | `environment?`, `flow` |
|
|
106
|
+
| `cancel_flow_run` | `environment?`, `flow`, `run` |
|
|
107
|
+
| `resubmit_flow_run` | `environment?`, `flow`, `run`, `trigger` |
|
|
108
|
+
| `add_flow_owner` | `environment?`, `flow`, `principalId`, `roleName` (`CanEdit`\|`CanView`) |
|
|
109
|
+
| `remove_flow_owner` | `environment?`, `flow`, `principalId` |
|
|
110
|
+
|
|
111
|
+
When `environment` is omitted, tools use `DEFAULT_ENVIRONMENT` if set, else the discovered
|
|
112
|
+
default environment (`isDefault: true`).
|
|
113
|
+
|
|
114
|
+
### Built-in (from SomaMCP)
|
|
115
|
+
|
|
116
|
+
- `info` MCP tool — server name, version, git SHA, capability counts.
|
|
117
|
+
- `report_feedback` — file API‑drift/bug reports as GitHub issues (`FEEDBACK_GITHUB_REPO`,
|
|
118
|
+
`GITHUB_TOKEN`).
|
|
119
|
+
- HTTP endpoints `/health`, `/health/detail`, `/info`, `/dashboard` (the detailed ones are
|
|
120
|
+
protected by `MCP_API_KEY` when set).
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
See [`.env.example`](.env.example) for the full list. Highlights: `AZURE_CLIENT_ID` (required),
|
|
125
|
+
`AZURE_TENANT_ID` (`common`), `AZURE_AUTH_MODE`, `TRANSPORT` (`stdio`\|`http`), `PORT`,
|
|
126
|
+
`ENABLE_WRITE_OPS`, `DEFAULT_ENVIRONMENT`, `MCP_API_KEY`, `TELEMETRY`, `TOKEN_CACHE_PATH`.
|
|
127
|
+
|
|
128
|
+
## Transports & deployment
|
|
129
|
+
|
|
130
|
+
| Scenario | Transport | Auth | Notes |
|
|
131
|
+
| ----------------------- | --------- | -------------------------------------- | ---------------------------------------------------------------------------- |
|
|
132
|
+
| Local agent | `stdio` | device-code | Primary. Full reach. `pnpm dev:stdio`. |
|
|
133
|
+
| Docker, single operator | `http` | device-code + **mounted token volume** | Auth once via `docker logs`; persists. Full reach. `docker compose up`. |
|
|
134
|
+
| Docker, unattended | `http` | `clientCredentials` | No human, but **no personal flows**; verify it can mint a Flow token at all. |
|
|
135
|
+
|
|
136
|
+
> **v2:** per‑user browser OAuth over HTTP via FastMCP's `AzureProvider` + disk token cache
|
|
137
|
+
> (the upstream token surfaces on the session). Reachable through SomaMCP's `backendOptions`
|
|
138
|
+
> passthrough without a fork — not wired in v1.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Docker (single-operator device-code with a persisted token volume)
|
|
142
|
+
AZURE_CLIENT_ID=... docker compose up --build
|
|
143
|
+
docker compose logs -f # grab the device code on first run
|
|
144
|
+
curl -s http://localhost:3333/health
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pnpm validate # format + lint + typecheck + test + build
|
|
151
|
+
pnpm test # vitest (unit)
|
|
152
|
+
pnpm dev # http transport, watch
|
|
153
|
+
pnpm dev:stdio # stdio transport, watch
|
|
154
|
+
pnpm build # tsdown -> dist/
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Integration tests that hit a real tenant live under `test/integration/` and run only with
|
|
158
|
+
`INTEGRATION=1` (see that folder's README). CI runs unit tests only.
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
- **Device code never grants a token / "device-code sign-in failed for all scope candidates"**
|
|
163
|
+
→ the Flow audience isn't grantable to your app. See "Token audience" and `docs/api-notes.md`.
|
|
164
|
+
- **`auth error` on every call** → token cache stale; restart to re‑auth, or delete
|
|
165
|
+
`TOKEN_CACHE_PATH`.
|
|
166
|
+
- **`not found` on a known flow** → wrong environment; run `list_environments` / `list_flows`
|
|
167
|
+
first. The flow `name` is the GUID, not the display name.
|
|
168
|
+
- **`forbidden`** → the signed‑in user lacks permission on that flow.
|
|
169
|
+
- **Empty `list_flows`** in `clientCredentials` mode → app‑only can't see personal flows; use
|
|
170
|
+
`interactive`.
|
|
171
|
+
- **An endpoint 404/410s unexpectedly** → Microsoft may have moved the api‑version; check the
|
|
172
|
+
portal's network tab and pin a newer `api-version` (see `docs/api-notes.md`).
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
**Sponsored by <a href="https://sapientsai.com/"><img src="https://sapientsai.com/images/logo.svg" alt="SapientsAI" width="20" style="vertical-align: middle;"> SapientsAI</a>** — Building agentic AI for businesses
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{dirname as e}from"node:path";import{chmod as t,mkdir as n,readFile as r,writeFile as i}from"node:fs/promises";const a=a=>({beforeCacheAccess:async e=>{try{let t=await r(a,`utf-8`);e.tokenCache.deserialize(t)}catch{}},afterCacheAccess:async r=>{r.cacheHasChanged&&(await n(e(a),{recursive:!0}),await i(a,r.tokenCache.serialize(),{mode:384}),await t(a,384))}});export{a as createFileCachePlugin};
|
|
2
|
+
//# sourceMappingURL=token-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.js","names":[],"sources":["../../src/auth/token-cache.ts"],"sourcesContent":["/**\n * File-backed MSAL cache plugin.\n *\n * Persists MSAL's own serialized token cache (which holds the refresh token, enabling\n * silent refresh) to disk at mode 0600. The path is configurable via TOKEN_CACHE_PATH so\n * it can be pointed at a mounted volume in container deployments.\n *\n * Used only by the interactive (device-code) path. The clientCredentials path has no\n * refresh token and keeps its access token in memory only.\n */\n\nimport { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\"\nimport { dirname } from \"node:path\"\n\nimport type { ICachePlugin, TokenCacheContext } from \"@azure/msal-node\"\n\nexport const createFileCachePlugin = (cachePath: string): ICachePlugin => ({\n beforeCacheAccess: async (context: TokenCacheContext): Promise<void> => {\n try {\n const data = await readFile(cachePath, \"utf-8\")\n context.tokenCache.deserialize(data)\n } catch {\n // No cache file yet (first run) — MSAL starts with an empty cache.\n }\n },\n afterCacheAccess: async (context: TokenCacheContext): Promise<void> => {\n if (!context.cacheHasChanged) return\n await mkdir(dirname(cachePath), { recursive: true })\n await writeFile(cachePath, context.tokenCache.serialize(), { mode: 0o600 })\n // writeFile's mode is only honored on file creation; enforce on existing files too.\n await chmod(cachePath, 0o600)\n },\n})\n"],"mappings":"qHAgBA,MAAa,EAAyB,IAAqC,CACzE,kBAAmB,KAAO,IAA8C,CACtE,GAAI,CACF,IAAM,EAAO,MAAM,EAAS,EAAW,OAAO,EAC9C,EAAQ,WAAW,YAAY,CAAI,CACrC,MAAQ,CAER,CACF,EACA,iBAAkB,KAAO,IAA8C,CAChE,EAAQ,kBACb,MAAM,EAAM,EAAQ,CAAS,EAAG,CAAE,UAAW,EAAK,CAAC,EACnD,MAAM,EAAU,EAAW,EAAQ,WAAW,UAAU,EAAG,CAAE,KAAM,GAAM,CAAC,EAE1E,MAAM,EAAM,EAAW,GAAK,EAC9B,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{FLOW_SCOPE_CANDIDATES as e}from"./types.js";import{leftAuth as t}from"../errors.js";import{createFileCachePlugin as n}from"./token-cache.js";import{Right as r,Try as i}from"functype";import{ConfidentialClientApplication as a,PublicClientApplication as o}from"@azure/msal-node";const s=e=>e instanceof Error?e.message:String(e),c=e=>Date.now()<e.expiresAt-3e5,l=(e,t)=>({accessToken:e.accessToken,expiresAt:e.expiresOn?e.expiresOn.getTime():0,scopeUsed:t,account:e.account??void 0}),u=e=>`https://login.microsoftonline.com/${e}`,d=t=>{let n=new Set,r=[];for(let i of[t.flowScopes,...e]){let e=[...i],t=e.join(` `);e.length>0&&!n.has(t)&&(n.add(t),r.push(e))}return r},f=(e,n)=>{let o=new a({auth:{clientId:e.clientId,authority:u(e.tenantId),clientSecret:e.clientSecret}}),d={current:null};return{getToken:async()=>{if(d.current&&c(d.current))return r(d.current.accessToken);let a=[...e.flowScopes];return(await i.fromPromise(o.acquireTokenByClientCredential({scopes:a}))).fold(e=>t(`client-credentials token request failed: ${s(e)}`,`client_credentials_failed`),e=>e?(d.current=l(e,a),n(`[auth] acquired Flow token (client credentials) using scope: ${a.join(` `)}`),r(e.accessToken)):t(`client-credentials flow returned no token`,`client_credentials_failed`))}}},p=(e,a)=>{let f=new o({auth:{clientId:e.clientId,authority:u(e.tenantId)},cache:{cachePlugin:n(e.tokenCachePath)}}),p={current:null},m=async()=>{let n=(await i.fromPromise(f.getTokenCache().getAllAccounts())).fold(()=>[],e=>e),a=p.current?.account??n[0];if(!a)return t(`no cached account for silent refresh`,`silent_failed`);let o=p.current?.scopeUsed??[...e.flowScopes];return(await i.fromPromise(f.acquireTokenSilent({account:a,scopes:o}))).fold(e=>t(`silent token refresh failed: ${s(e)}`,`silent_failed`),e=>(p.current=l(e,o),r(e.accessToken)))},h=async()=>{let n=`no scope candidates configured`;for(let t of d(e)){let e=(await i.fromPromise(f.acquireTokenByDeviceCode({scopes:t,deviceCodeCallback:e=>a(e.message)}))).fold(e=>(n=s(e),null),e=>e?(p.current=l(e,t),a(`[auth] acquired Flow token (device code) using scope: ${t.join(` `)}`),r(e.accessToken)):(n=`device-code flow returned no token`,null));if(e)return e}return t(`device-code sign-in failed for all scope candidates. Last error: ${n}`,`device_code_failed`)};return{getToken:async()=>{if(p.current&&c(p.current))return r(p.current.accessToken);let e=await m();return e.isRight()?e:h()}}},m=(e,t)=>{let n=t?.log??(e=>console.error(e));return e.authMode===`clientCredentials`?f(e,n):p(e,n)};export{m as createTokenManager};
|
|
2
|
+
//# sourceMappingURL=token-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-manager.js","names":[],"sources":["../../src/auth/token-manager.ts"],"sourcesContent":["/**\n * Token acquisition for the Flow API audience.\n *\n * Exposes a single seam — {@link TokenProvider.getToken} — so the Flow API client never\n * cares *how* a token was obtained. v1 provides two implementations:\n *\n * - interactive (default): MSAL device-code flow over stdio, with silent refresh from a\n * file-persisted cache. Full per-user reach (sees personal \"My Flows\").\n * - clientCredentials: app-only via client secret, in-memory token only. Unattended, but\n * limited Flow reach (see README).\n *\n * A future v2 HTTP/per-user path (FastMCP AzureProvider) can satisfy the same seam with\n * `() => Right(session.accessToken)` — no change to the client or tools.\n *\n * The Flow-audience scope that actually works for a custom public client is unknown until\n * first sign-in (see {@link FLOW_SCOPE_CANDIDATES}); the device-code path tries candidates\n * in order and logs the winner.\n */\n\nimport {\n type AccountInfo,\n type AuthenticationResult,\n ConfidentialClientApplication,\n PublicClientApplication,\n} from \"@azure/msal-node\"\nimport { type Either, Right, Try } from \"functype\"\n\nimport { type AuthError, leftAuth } from \"../errors.js\"\nimport { createFileCachePlugin } from \"./token-cache.js\"\nimport { FLOW_SCOPE_CANDIDATES, type ServerConfig } from \"./types.js\"\n\nexport type TokenProvider = {\n getToken: () => Promise<Either<AuthError, string>>\n}\n\nexport type TokenManagerOptions = {\n /** Sink for device-code prompts and the winning-scope diagnostic. Defaults to stderr. */\n log?: (message: string) => void\n}\n\nconst REFRESH_BUFFER_MS = 5 * 60 * 1000\n\ntype Current = {\n accessToken: string\n expiresAt: number\n scopeUsed: string[]\n account?: AccountInfo\n}\n\nconst errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e))\n\nconst isValid = (c: Current): boolean => Date.now() < c.expiresAt - REFRESH_BUFFER_MS\n\nconst toCurrent = (result: AuthenticationResult, scopeUsed: string[]): Current => ({\n accessToken: result.accessToken,\n expiresAt: result.expiresOn ? result.expiresOn.getTime() : 0,\n scopeUsed,\n account: result.account ?? undefined,\n})\n\nconst authorityFor = (tenantId: string): string => `https://login.microsoftonline.com/${tenantId}`\n\n/** Distinct scope sets to attempt: the configured one first, then the documented candidates. */\nconst scopeSets = (config: ServerConfig): string[][] => {\n const seen = new Set<string>()\n const out: string[][] = []\n for (const set of [config.flowScopes, ...FLOW_SCOPE_CANDIDATES]) {\n const arr = [...set]\n const key = arr.join(\" \")\n if (arr.length > 0 && !seen.has(key)) {\n seen.add(key)\n out.push(arr)\n }\n }\n return out\n}\n\n// ── clientCredentials (app-only) ──────────────────────────────────────\n\nconst createClientCredentialsManager = (config: ServerConfig, log: (m: string) => void): TokenProvider => {\n const app = new ConfidentialClientApplication({\n auth: { clientId: config.clientId, authority: authorityFor(config.tenantId), clientSecret: config.clientSecret },\n })\n const state: { current: Current | null } = { current: null }\n\n const getToken = async (): Promise<Either<AuthError, string>> => {\n if (state.current && isValid(state.current)) return Right(state.current.accessToken)\n const scopes = [...config.flowScopes]\n const result = await Try.fromPromise(app.acquireTokenByClientCredential({ scopes }))\n return result.fold(\n (err) => leftAuth(`client-credentials token request failed: ${errMsg(err)}`, \"client_credentials_failed\"),\n (auth) => {\n if (!auth) return leftAuth(\"client-credentials flow returned no token\", \"client_credentials_failed\")\n state.current = toCurrent(auth, scopes)\n log(`[auth] acquired Flow token (client credentials) using scope: ${scopes.join(\" \")}`)\n return Right(auth.accessToken)\n },\n )\n }\n\n return { getToken }\n}\n\n// ── interactive (device code) ─────────────────────────────────────────\n\nconst createInteractiveManager = (config: ServerConfig, log: (m: string) => void): TokenProvider => {\n const app = new PublicClientApplication({\n auth: { clientId: config.clientId, authority: authorityFor(config.tenantId) },\n cache: { cachePlugin: createFileCachePlugin(config.tokenCachePath) },\n })\n const state: { current: Current | null } = { current: null }\n\n const trySilent = async (): Promise<Either<AuthError, string>> => {\n const accounts = (await Try.fromPromise(app.getTokenCache().getAllAccounts())).fold(\n () => [] as AccountInfo[],\n (a) => a,\n )\n const account = state.current?.account ?? accounts[0]\n if (!account) return leftAuth(\"no cached account for silent refresh\", \"silent_failed\")\n const scopes = state.current?.scopeUsed ?? [...config.flowScopes]\n const result = await Try.fromPromise(app.acquireTokenSilent({ account, scopes }))\n return result.fold(\n (err) => leftAuth(`silent token refresh failed: ${errMsg(err)}`, \"silent_failed\"),\n (auth) => {\n state.current = toCurrent(auth, scopes)\n return Right(auth.accessToken)\n },\n )\n }\n\n const tryDeviceCode = async (): Promise<Either<AuthError, string>> => {\n let lastError = \"no scope candidates configured\"\n for (const scopes of scopeSets(config)) {\n const result = await Try.fromPromise(\n app.acquireTokenByDeviceCode({ scopes, deviceCodeCallback: (response) => log(response.message) }),\n )\n const outcome = result.fold(\n (err): Either<AuthError, string> | null => {\n lastError = errMsg(err)\n return null\n },\n (auth): Either<AuthError, string> | null => {\n if (!auth) {\n lastError = \"device-code flow returned no token\"\n return null\n }\n state.current = toCurrent(auth, scopes)\n log(`[auth] acquired Flow token (device code) using scope: ${scopes.join(\" \")}`)\n return Right(auth.accessToken)\n },\n )\n if (outcome) return outcome\n }\n return leftAuth(\n `device-code sign-in failed for all scope candidates. Last error: ${lastError}`,\n \"device_code_failed\",\n )\n }\n\n const getToken = async (): Promise<Either<AuthError, string>> => {\n if (state.current && isValid(state.current)) return Right(state.current.accessToken)\n const silent = await trySilent()\n if (silent.isRight()) return silent\n return tryDeviceCode()\n }\n\n return { getToken }\n}\n\nexport const createTokenManager = (config: ServerConfig, opts?: TokenManagerOptions): TokenProvider => {\n const log = opts?.log ?? ((message: string): void => console.error(message))\n return config.authMode === \"clientCredentials\"\n ? createClientCredentialsManager(config, log)\n : createInteractiveManager(config, log)\n}\n"],"mappings":"4RAwCA,MASM,EAAU,GAAwB,aAAa,MAAQ,EAAE,QAAU,OAAO,CAAC,EAE3E,EAAW,GAAwB,KAAK,IAAI,EAAI,EAAE,UAAY,IAE9D,GAAa,EAA8B,KAAkC,CACjF,YAAa,EAAO,YACpB,UAAW,EAAO,UAAY,EAAO,UAAU,QAAQ,EAAI,EAC3D,YACA,QAAS,EAAO,SAAW,IAAA,EAC7B,GAEM,EAAgB,GAA6B,qCAAqC,IAGlF,EAAa,GAAqC,CACtD,IAAM,EAAO,IAAI,IACX,EAAkB,CAAC,EACzB,IAAK,IAAM,IAAO,CAAC,EAAO,WAAY,GAAG,CAAqB,EAAG,CAC/D,IAAM,EAAM,CAAC,GAAG,CAAG,EACb,EAAM,EAAI,KAAK,GAAG,EACpB,EAAI,OAAS,GAAK,CAAC,EAAK,IAAI,CAAG,IACjC,EAAK,IAAI,CAAG,EACZ,EAAI,KAAK,CAAG,EAEhB,CACA,OAAO,CACT,EAIM,GAAkC,EAAsB,IAA4C,CACxG,IAAM,EAAM,IAAI,EAA8B,CAC5C,KAAM,CAAE,SAAU,EAAO,SAAU,UAAW,EAAa,EAAO,QAAQ,EAAG,aAAc,EAAO,YAAa,CACjH,CAAC,EACK,EAAqC,CAAE,QAAS,IAAK,EAiB3D,MAAO,CAAE,kBAfwD,CAC/D,GAAI,EAAM,SAAW,EAAQ,EAAM,OAAO,EAAG,OAAO,EAAM,EAAM,QAAQ,WAAW,EACnF,IAAM,EAAS,CAAC,GAAG,EAAO,UAAU,EAEpC,OAAO,MADc,EAAI,YAAY,EAAI,+BAA+B,CAAE,QAAO,CAAC,CAAC,GACrE,KACX,GAAQ,EAAS,4CAA4C,EAAO,CAAG,IAAK,2BAA2B,EACvG,GACM,GACL,EAAM,QAAU,EAAU,EAAM,CAAM,EACtC,EAAI,gEAAgE,EAAO,KAAK,GAAG,GAAG,EAC/E,EAAM,EAAK,WAAW,GAHX,EAAS,4CAA6C,2BAA2B,CAKvG,CACF,CAEkB,CACpB,EAIM,GAA4B,EAAsB,IAA4C,CAClG,IAAM,EAAM,IAAI,EAAwB,CACtC,KAAM,CAAE,SAAU,EAAO,SAAU,UAAW,EAAa,EAAO,QAAQ,CAAE,EAC5E,MAAO,CAAE,YAAa,EAAsB,EAAO,cAAc,CAAE,CACrE,CAAC,EACK,EAAqC,CAAE,QAAS,IAAK,EAErD,EAAY,SAAgD,CAChE,IAAM,GAAY,MAAM,EAAI,YAAY,EAAI,cAAc,EAAE,eAAe,CAAC,GAAG,SACvE,CAAC,EACN,GAAM,CACT,EACM,EAAU,EAAM,SAAS,SAAW,EAAS,GACnD,GAAI,CAAC,EAAS,OAAO,EAAS,uCAAwC,eAAe,EACrF,IAAM,EAAS,EAAM,SAAS,WAAa,CAAC,GAAG,EAAO,UAAU,EAEhE,OAAO,MADc,EAAI,YAAY,EAAI,mBAAmB,CAAE,UAAS,QAAO,CAAC,CAAC,GAClE,KACX,GAAQ,EAAS,gCAAgC,EAAO,CAAG,IAAK,eAAe,EAC/E,IACC,EAAM,QAAU,EAAU,EAAM,CAAM,EAC/B,EAAM,EAAK,WAAW,EAEjC,CACF,EAEM,EAAgB,SAAgD,CACpE,IAAI,EAAY,iCAChB,IAAK,IAAM,KAAU,EAAU,CAAM,EAAG,CAItC,IAAM,GAAU,MAHK,EAAI,YACvB,EAAI,yBAAyB,CAAE,SAAQ,mBAAqB,GAAa,EAAI,EAAS,OAAO,CAAE,CAAC,CAClG,GACuB,KACpB,IACC,EAAY,EAAO,CAAG,EACf,MAER,GACM,GAIL,EAAM,QAAU,EAAU,EAAM,CAAM,EACtC,EAAI,yDAAyD,EAAO,KAAK,GAAG,GAAG,EACxE,EAAM,EAAK,WAAW,IAL3B,EAAY,qCACL,KAMb,EACA,GAAI,EAAS,OAAO,CACtB,CACA,OAAO,EACL,oEAAoE,IACpE,oBACF,CACF,EASA,MAAO,CAAE,kBAPwD,CAC/D,GAAI,EAAM,SAAW,EAAQ,EAAM,OAAO,EAAG,OAAO,EAAM,EAAM,QAAQ,WAAW,EACnF,IAAM,EAAS,MAAM,EAAU,EAE/B,OADI,EAAO,QAAQ,EAAU,EACtB,EAAc,CACvB,CAEkB,CACpB,EAEa,GAAsB,EAAsB,IAA8C,CACrG,IAAM,EAAM,GAAM,MAAS,GAA0B,QAAQ,MAAM,CAAO,GAC1E,OAAO,EAAO,WAAa,oBACvB,EAA+B,EAAQ,CAAG,EAC1C,EAAyB,EAAQ,CAAG,CAC1C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as TelemetrySink, i as ServerConfig, n as FLOW_RESOURCE, o as TokenSet, r as FLOW_SCOPE_CANDIDATES, s as TransportKind, t as AuthMode } from "../types-HGbUI9Dc.js";
|
|
2
|
+
export { AuthMode, FLOW_RESOURCE, FLOW_SCOPE_CANDIDATES, ServerConfig, TelemetrySink, TokenSet, TransportKind };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`https://service.flow.microsoft.com/`,t=[[`https://service.flow.microsoft.com//.default`],[`https://service.flow.microsoft.com/.default`],[`https://service.flow.microsoft.com/User`]];export{e as FLOW_RESOURCE,t as FLOW_SCOPE_CANDIDATES};
|
|
2
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/auth/types.ts"],"sourcesContent":["/**\n * Auth-layer types and the Flow API resource/scope constants.\n *\n * The exact OAuth scope that yields a token for the `service.flow.microsoft.com` audience\n * from a *custom* public-client app registration is the #1 implementation unknown (see\n * README \"Token audience\"). {@link FLOW_SCOPE_CANDIDATES} is tried in order at first\n * device-code sign-in; the winner is logged in startup telemetry and should be pinned in\n * `FLOW_SCOPES` / documented in docs/api-notes.md.\n */\n\nimport type { AccountInfo } from \"@azure/msal-node\"\n\n// ── Auth mode ─────────────────────────────────────────────────────────\n\nexport type AuthMode = \"interactive\" | \"clientCredentials\"\n\n// ── Flow API audience ─────────────────────────────────────────────────\n\n/** Resource/audience for the Power Automate management API. */\nexport const FLOW_RESOURCE = \"https://service.flow.microsoft.com/\" as const\n\n/**\n * Ordered scope candidates to try for the Flow audience under a public client.\n * The double-slash form appears in several Microsoft samples and is intentional.\n */\nexport const FLOW_SCOPE_CANDIDATES: ReadonlyArray<ReadonlyArray<string>> = [\n [\"https://service.flow.microsoft.com//.default\"],\n [\"https://service.flow.microsoft.com/.default\"],\n [\"https://service.flow.microsoft.com/User\"],\n]\n\n// ── Token cache shape ─────────────────────────────────────────────────\n\nexport type TokenSet = {\n readonly accessToken: string\n /** Epoch milliseconds. */\n readonly expiresAt: number\n /** The scope set that actually minted this token (pinned for reuse + diagnostics). */\n readonly scopeUsed: ReadonlyArray<string>\n /** MSAL account, needed for silent refresh in interactive mode. */\n readonly account?: AccountInfo\n}\n\n// ── Server configuration ──────────────────────────────────────────────\n\nexport type TransportKind = \"stdio\" | \"http\"\n\nexport type TelemetrySink = \"console\" | \"file\"\n\nexport type ServerConfig = {\n // Auth\n readonly clientId: string\n readonly tenantId: string\n readonly authMode: AuthMode\n readonly clientSecret?: string\n readonly flowResource: string\n /** Primary scope set; defaults to `<flowResource>.default`. Overridable via FLOW_SCOPES. */\n readonly flowScopes: ReadonlyArray<string>\n readonly tokenCachePath: string\n\n // Transport\n readonly transport: TransportKind\n readonly port: number\n readonly endpoint: `/${string}`\n /** Bearer token that protects the HTTP operational endpoints (health/info/dashboard). */\n readonly mcpApiKey?: string\n\n // Behavior\n readonly enableWriteOps: boolean\n readonly defaultEnvironment?: string\n\n // Telemetry\n readonly telemetry: ReadonlyArray<TelemetrySink>\n readonly telemetryFilePath: string\n readonly logLevel: string\n\n // Feedback (report_feedback tool target — \"owner/repo\")\n readonly feedbackRepo: string\n}\n"],"mappings":"AAmBA,MAAa,EAAgB,sCAMhB,EAA8D,CACzE,CAAC,8CAA8C,EAC/C,CAAC,6CAA6C,EAC9C,CAAC,yCAAyC,CAC5C"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { u as flowApiError } from "../../errors-BsWrc7px.js";
|
|
2
|
+
import { i as createFlowApiClient, n as HttpMethod, r as RequestOptions, t as FlowApiClient } from "../../client-DCWt6ssY.js";
|
|
3
|
+
export { FlowApiClient, HttpMethod, RequestOptions, createFlowApiClient, flowApiError };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{flowApiError as e,leftFlowApi as t}from"../../errors.js";import{Right as n,Try as r}from"functype";const i=e=>e instanceof Error?e.message:String(e),a=e=>e===400?`bad_request`:e===401?`auth_expired`:e===403?`forbidden`:e===404?`not_found`:e===429?`rate_limited`:e>=500?`server_error`:`unsupported_api_drift`,o=(e,t)=>{let n=new URL(`https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple${e}`);if(n.searchParams.set(`api-version`,t.apiVersion??`2016-11-01`),t.query)for(let[e,r]of Object.entries(t.query))r!==void 0&&n.searchParams.set(e,String(r));return n.toString()},s=e=>({request:async(s,c,l={})=>{let u=await e.tokenProvider.getToken();if(u.isLeft())return t(`auth_expired`,`could not acquire a Flow API token: ${u.fold(e=>e.message,()=>``)}`);let d=u.orElse(``),f=l.body!==void 0,p={method:s,headers:{Authorization:`Bearer ${d}`,Accept:`application/json`,...f?{"Content-Type":`application/json`}:{}},...f?{body:JSON.stringify(l.body)}:{}};return(await r.fromPromise(fetch(o(c,l),p))).fold(async e=>t(`network`,`${s} ${c} could not reach api.flow.microsoft.com: ${i(e)}`),async e=>{if(!e.ok){let n=(await r.fromPromise(e.text())).orElse(``);return t(a(e.status),`${s} ${c} returned ${e.status}`,{status:e.status,detail:n})}return e.status===204?n(void 0):(await r.fromPromise(e.json())).fold(e=>t(`unsupported_api_drift`,`${s} ${c} returned an unparseable body: ${i(e)}`),e=>n(e))})}});export{s as createFlowApiClient,e as flowApiError};
|
|
2
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/backend/flow-api/client.ts"],"sourcesContent":["/**\n * Authenticated fetch wrapper for Microsoft's *unofficial* Power Automate management API\n * (`api.flow.microsoft.com/providers/Microsoft.ProcessSimple`).\n *\n * Injects the bearer token from the {@link TokenProvider} seam and maps HTTP status codes\n * to typed {@link FlowApiError}s. Every method returns `Either<FlowApiError, T>` — no throws.\n */\n\nimport { type Either, Right, Try } from \"functype\"\n\nimport type { TokenProvider } from \"../../auth/token-manager.js\"\nimport { type FlowApiError, flowApiError, type FlowApiErrorKind, leftFlowApi } from \"../../errors.js\"\n\nconst BASE_URL = \"https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple\"\nconst DEFAULT_API_VERSION = \"2016-11-01\"\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\"\n\nexport type RequestOptions = {\n query?: Record<string, string | number | boolean | undefined>\n body?: unknown\n /** Override the default 2016-11-01 api-version for an endpoint that needs a newer one. */\n apiVersion?: string\n}\n\nexport type FlowApiClient = {\n request: <T>(method: HttpMethod, path: string, options?: RequestOptions) => Promise<Either<FlowApiError, T>>\n}\n\nconst errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e))\n\nconst statusToKind = (status: number): FlowApiErrorKind => {\n if (status === 400) return \"bad_request\"\n if (status === 401) return \"auth_expired\"\n if (status === 403) return \"forbidden\"\n if (status === 404) return \"not_found\"\n if (status === 429) return \"rate_limited\"\n if (status >= 500) return \"server_error\"\n return \"unsupported_api_drift\"\n}\n\nconst buildUrl = (path: string, options: RequestOptions): string => {\n const url = new URL(`${BASE_URL}${path}`)\n url.searchParams.set(\"api-version\", options.apiVersion ?? DEFAULT_API_VERSION)\n if (options.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) url.searchParams.set(key, String(value))\n }\n }\n return url.toString()\n}\n\nexport const createFlowApiClient = (deps: { tokenProvider: TokenProvider }): FlowApiClient => {\n const request = async <T>(\n method: HttpMethod,\n path: string,\n options: RequestOptions = {},\n ): Promise<Either<FlowApiError, T>> => {\n const tokenE = await deps.tokenProvider.getToken()\n if (tokenE.isLeft()) {\n const message = tokenE.fold(\n (e) => e.message,\n () => \"\",\n )\n return leftFlowApi<T>(\"auth_expired\", `could not acquire a Flow API token: ${message}`)\n }\n const token = tokenE.orElse(\"\")\n\n const hasBody = options.body !== undefined\n const init: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n ...(hasBody ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n }\n\n const fetchResult = await Try.fromPromise(fetch(buildUrl(path, options), init))\n return fetchResult.fold(\n async (err): Promise<Either<FlowApiError, T>> =>\n leftFlowApi<T>(\"network\", `${method} ${path} could not reach api.flow.microsoft.com: ${errMsg(err)}`),\n async (response): Promise<Either<FlowApiError, T>> => {\n if (!response.ok) {\n const detail = (await Try.fromPromise(response.text())).orElse(\"\")\n return leftFlowApi<T>(statusToKind(response.status), `${method} ${path} returned ${response.status}`, {\n status: response.status,\n detail,\n })\n }\n // 204 No Content (common for start/stop/cancel/delete) — nothing to parse.\n if (response.status === 204) return Right(undefined as T)\n\n const parsed = await Try.fromPromise(response.json())\n return parsed.fold(\n (err) =>\n leftFlowApi<T>(\"unsupported_api_drift\", `${method} ${path} returned an unparseable body: ${errMsg(err)}`),\n (json) => Right(json as T),\n )\n },\n )\n }\n\n return { request }\n}\n\nexport { flowApiError }\n"],"mappings":"0GAaA,MAgBM,EAAU,GAAwB,aAAa,MAAQ,EAAE,QAAU,OAAO,CAAC,EAE3E,EAAgB,GAChB,IAAW,IAAY,cACvB,IAAW,IAAY,eACvB,IAAW,IAAY,YACvB,IAAW,IAAY,YACvB,IAAW,IAAY,eACvB,GAAU,IAAY,eACnB,wBAGH,GAAY,EAAc,IAAoC,CAClE,IAAM,EAAM,IAAI,IAAI,mEAAc,GAAM,EAExC,GADA,EAAI,aAAa,IAAI,cAAe,EAAQ,YAAc,YAAmB,EACzE,EAAQ,UACL,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAQ,KAAK,EACjD,IAAU,IAAA,IAAW,EAAI,aAAa,IAAI,EAAK,OAAO,CAAK,CAAC,EAGpE,OAAO,EAAI,SAAS,CACtB,EAEa,EAAuB,IAoD3B,CAAE,cAlDP,EACA,EACA,EAA0B,CAAC,IACU,CACrC,IAAM,EAAS,MAAM,EAAK,cAAc,SAAS,EACjD,GAAI,EAAO,OAAO,EAKhB,OAAO,EAAe,eAAgB,uCAJtB,EAAO,KACpB,GAAM,EAAE,YACH,EAE2E,GAAG,EAExF,IAAM,EAAQ,EAAO,OAAO,EAAE,EAExB,EAAU,EAAQ,OAAS,IAAA,GAC3B,EAAoB,CACxB,SACA,QAAS,CACP,cAAe,UAAU,IACzB,OAAQ,mBACR,GAAI,EAAU,CAAE,eAAgB,kBAAmB,EAAI,CAAC,CAC1D,EACA,GAAI,EAAU,CAAE,KAAM,KAAK,UAAU,EAAQ,IAAI,CAAE,EAAI,CAAC,CAC1D,EAGA,OAAO,MADmB,EAAI,YAAY,MAAM,EAAS,EAAM,CAAO,EAAG,CAAI,CAAC,GAC3D,KACjB,KAAO,IACL,EAAe,UAAW,GAAG,EAAO,GAAG,EAAK,2CAA2C,EAAO,CAAG,GAAG,EACtG,KAAO,IAA+C,CACpD,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,GAAU,MAAM,EAAI,YAAY,EAAS,KAAK,CAAC,GAAG,OAAO,EAAE,EACjE,OAAO,EAAe,EAAa,EAAS,MAAM,EAAG,GAAG,EAAO,GAAG,EAAK,YAAY,EAAS,SAAU,CACpG,OAAQ,EAAS,OACjB,QACF,CAAC,CACH,CAKA,OAHI,EAAS,SAAW,IAAY,EAAM,IAAA,EAAc,GAGjD,MADc,EAAI,YAAY,EAAS,KAAK,CAAC,GACtC,KACX,GACC,EAAe,wBAAyB,GAAG,EAAO,GAAG,EAAK,iCAAiC,EAAO,CAAG,GAAG,EACzG,GAAS,EAAM,CAAS,CAC3B,CACF,CACF,CACF,CAEiB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { a as FlowApiError } from "../../errors-BsWrc7px.js";
|
|
2
|
+
import { t as FlowApiClient } from "../../client-DCWt6ssY.js";
|
|
3
|
+
import { t as Connection } from "../../types-DRNRhdJl.js";
|
|
4
|
+
import { Either } from "functype";
|
|
5
|
+
|
|
6
|
+
//#region src/backend/flow-api/connections.d.ts
|
|
7
|
+
declare const listConnections: (client: FlowApiClient, env: string) => Promise<Either<FlowApiError, Connection[]>>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { listConnections };
|
|
10
|
+
//# sourceMappingURL=connections.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>e.api?.name?e.api.name:e.apiId?e.apiId.split(`/`).filter(Boolean).pop()??e.apiId:``,t=e=>e.statuses?.[0]?.status??e.status??`Unknown`,n=n=>{let r=n.properties??{};return{name:n.name??``,apiName:e(r),displayName:r.displayName??n.name??``,status:t(r),accountName:r.accountName??r.createdBy?.email??r.createdBy?.displayName,expiresAt:r.expirationTime??r.expiresAt}},r=async(e,t)=>(await e.request(`GET`,`/environments/${encodeURIComponent(t)}/connections`)).map(e=>(e.value??[]).map(n));export{r as listConnections};
|
|
2
|
+
//# sourceMappingURL=connections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.js","names":[],"sources":["../../../src/backend/flow-api/connections.ts"],"sourcesContent":["/**\n * Connection listing. `GET /environments/{env}/connections`.\n *\n * `status !== \"Connected\"` flags a broken/expired connection — the common root cause of a\n * flow that silently stops working.\n */\n\nimport type { Either } from \"functype\"\n\nimport type { FlowApiError } from \"../../errors.js\"\nimport type { Connection } from \"../types.js\"\nimport type { FlowApiClient } from \"./client.js\"\n\ntype RawApi = { name?: string; displayName?: string }\ntype RawStatus = { status?: string }\n\ntype RawConnectionProperties = {\n displayName?: string\n apiId?: string\n api?: RawApi\n statuses?: RawStatus[]\n status?: string\n accountName?: string\n createdBy?: { displayName?: string; email?: string }\n expirationTime?: string\n expiresAt?: string\n}\n\ntype RawConnection = { name?: string; properties?: RawConnectionProperties }\n\ntype ListEnvelope<T> = { value?: T[] }\n\nconst apiNameOf = (p: RawConnectionProperties): string => {\n if (p.api?.name) return p.api.name\n if (p.apiId) return p.apiId.split(\"/\").filter(Boolean).pop() ?? p.apiId\n return \"\"\n}\n\nconst statusOf = (p: RawConnectionProperties): string => p.statuses?.[0]?.status ?? p.status ?? \"Unknown\"\n\nconst mapConnection = (raw: RawConnection): Connection => {\n const p = raw.properties ?? {}\n return {\n name: raw.name ?? \"\",\n apiName: apiNameOf(p),\n displayName: p.displayName ?? raw.name ?? \"\",\n status: statusOf(p),\n accountName: p.accountName ?? p.createdBy?.email ?? p.createdBy?.displayName,\n expiresAt: p.expirationTime ?? p.expiresAt,\n }\n}\n\nexport const listConnections = async (\n client: FlowApiClient,\n env: string,\n): Promise<Either<FlowApiError, Connection[]>> => {\n const result = await client.request<ListEnvelope<RawConnection>>(\n \"GET\",\n `/environments/${encodeURIComponent(env)}/connections`,\n )\n return result.map((envelope) => (envelope.value ?? []).map(mapConnection))\n}\n"],"mappings":"AAgCA,MAAM,EAAa,GACb,EAAE,KAAK,KAAa,EAAE,IAAI,KAC1B,EAAE,MAAc,EAAE,MAAM,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,GAAK,EAAE,MAC3D,GAGH,EAAY,GAAuC,EAAE,WAAW,IAAI,QAAU,EAAE,QAAU,UAE1F,EAAiB,GAAmC,CACxD,IAAM,EAAI,EAAI,YAAc,CAAC,EAC7B,MAAO,CACL,KAAM,EAAI,MAAQ,GAClB,QAAS,EAAU,CAAC,EACpB,YAAa,EAAE,aAAe,EAAI,MAAQ,GAC1C,OAAQ,EAAS,CAAC,EAClB,YAAa,EAAE,aAAe,EAAE,WAAW,OAAS,EAAE,WAAW,YACjE,UAAW,EAAE,gBAAkB,EAAE,SACnC,CACF,EAEa,EAAkB,MAC7B,EACA,KAMO,MAJc,EAAO,QAC1B,MACA,iBAAiB,mBAAmB,CAAG,EAAE,aAC3C,GACc,IAAK,IAAc,EAAS,OAAS,CAAC,GAAG,IAAI,CAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { a as FlowApiError } from "../../errors-BsWrc7px.js";
|
|
2
|
+
import { t as FlowApiClient } from "../../client-DCWt6ssY.js";
|
|
3
|
+
import { n as Environment } from "../../types-DRNRhdJl.js";
|
|
4
|
+
import { Either } from "functype";
|
|
5
|
+
|
|
6
|
+
//#region src/backend/flow-api/environments.d.ts
|
|
7
|
+
declare const listEnvironments: (client: FlowApiClient) => Promise<Either<FlowApiError, Environment[]>>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { listEnvironments };
|
|
10
|
+
//# sourceMappingURL=environments.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>({id:e.name??``,name:e.name??``,displayName:e.properties?.displayName??e.name??``,location:e.location??``,isDefault:e.properties?.isDefault??!1}),t=async t=>(await t.request(`GET`,`/environments`)).map(t=>(t.value??[]).map(e));export{t as listEnvironments};
|
|
2
|
+
//# sourceMappingURL=environments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environments.js","names":[],"sources":["../../../src/backend/flow-api/environments.ts"],"sourcesContent":["/**\n * `GET /environments` — the Power Automate environments visible to the signed-in user.\n *\n * The default environment (id `Default-<tenantId>`) surfaces with `isDefault: true`; tools\n * that omit an explicit environment fall back to it.\n */\n\nimport type { Either } from \"functype\"\n\nimport type { FlowApiError } from \"../../errors.js\"\nimport type { Environment } from \"../types.js\"\nimport type { FlowApiClient } from \"./client.js\"\n\ntype RawEnvironment = {\n name?: string\n location?: string\n properties?: { displayName?: string; isDefault?: boolean }\n}\n\ntype ListEnvelope<T> = { value?: T[] }\n\nconst mapEnvironment = (raw: RawEnvironment): Environment => ({\n id: raw.name ?? \"\",\n name: raw.name ?? \"\",\n displayName: raw.properties?.displayName ?? raw.name ?? \"\",\n location: raw.location ?? \"\",\n isDefault: raw.properties?.isDefault ?? false,\n})\n\nexport const listEnvironments = async (client: FlowApiClient): Promise<Either<FlowApiError, Environment[]>> => {\n const result = await client.request<ListEnvelope<RawEnvironment>>(\"GET\", \"/environments\")\n return result.map((envelope) => (envelope.value ?? []).map(mapEnvironment))\n}\n"],"mappings":"AAqBA,MAAM,EAAkB,IAAsC,CAC5D,GAAI,EAAI,MAAQ,GAChB,KAAM,EAAI,MAAQ,GAClB,YAAa,EAAI,YAAY,aAAe,EAAI,MAAQ,GACxD,SAAU,EAAI,UAAY,GAC1B,UAAW,EAAI,YAAY,WAAa,EAC1C,GAEa,EAAmB,KAAO,KAE9B,MADc,EAAO,QAAsC,MAAO,eAAe,GAC1E,IAAK,IAAc,EAAS,OAAS,CAAC,GAAG,IAAI,CAAc,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>e?.userId??e?.email??e?.displayName,t=e=>e?Object.keys(e):[],n=t=>{let n=t.properties??{};return{name:t.name??``,displayName:n.displayName??t.name??``,state:n.state??`Unknown`,createdTime:n.createdTime??``,lastModifiedTime:n.lastModifiedTime??``,owner:e(n.creator)}},r=async(e,t,r)=>(await e.request(`GET`,`/environments/${encodeURIComponent(t)}/flows`)).map(e=>{let t=(e.value??[]).map(n),i=r?.owner;return i?t.filter(e=>e.owner===i):t}),i=async(e,r,i)=>(await e.request(`GET`,`/environments/${encodeURIComponent(r)}/flows/${encodeURIComponent(i)}`)).map(e=>{let r=e.properties?.definition;return{...n(e),definition:r,connectionReferences:e.properties?.connectionReferences,triggers:t(r?.triggers),actions:t(r?.actions)}}),a=(e,t,n)=>e.request(`POST`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/start`),o=(e,t,n)=>e.request(`POST`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/stop`);export{o as disableFlow,a as enableFlow,i as getFlow,r as listFlows};
|
|
2
|
+
//# sourceMappingURL=flows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flows.js","names":[],"sources":["../../../src/backend/flow-api/flows.ts"],"sourcesContent":["/**\n * Flow listing + detail. `GET /environments/{env}/flows[/{flow}]`.\n *\n * The owner filter matches the creator's userId/email (the API exposes the creator as an\n * object id, not a UPN — see docs/api-notes.md).\n */\n\nimport type { Either } from \"functype\"\n\nimport type { FlowApiError } from \"../../errors.js\"\nimport type { FlowDetail, FlowSummary } from \"../types.js\"\nimport type { FlowApiClient } from \"./client.js\"\n\ntype RawCreator = { userId?: string; email?: string; displayName?: string }\n\ntype RawDefinition = { triggers?: Record<string, unknown>; actions?: Record<string, unknown> }\n\ntype RawFlowProperties = {\n displayName?: string\n state?: string\n createdTime?: string\n lastModifiedTime?: string\n definition?: RawDefinition\n connectionReferences?: unknown\n creator?: RawCreator\n}\n\ntype RawFlow = { name?: string; properties?: RawFlowProperties }\n\ntype ListEnvelope<T> = { value?: T[] }\n\nexport type FlowFilter = { owner?: string }\n\nconst ownerOf = (creator?: RawCreator): string | undefined => creator?.userId ?? creator?.email ?? creator?.displayName\n\nconst namesOf = (record?: Record<string, unknown>): string[] => (record ? Object.keys(record) : [])\n\nconst mapSummary = (raw: RawFlow): FlowSummary => {\n const p = raw.properties ?? {}\n return {\n name: raw.name ?? \"\",\n displayName: p.displayName ?? raw.name ?? \"\",\n state: p.state ?? \"Unknown\",\n createdTime: p.createdTime ?? \"\",\n lastModifiedTime: p.lastModifiedTime ?? \"\",\n owner: ownerOf(p.creator),\n }\n}\n\nexport const listFlows = async (\n client: FlowApiClient,\n env: string,\n filter?: FlowFilter,\n): Promise<Either<FlowApiError, FlowSummary[]>> => {\n const result = await client.request<ListEnvelope<RawFlow>>(\"GET\", `/environments/${encodeURIComponent(env)}/flows`)\n return result.map((envelope) => {\n const flows = (envelope.value ?? []).map(mapSummary)\n const owner = filter?.owner\n return owner ? flows.filter((f) => f.owner === owner) : flows\n })\n}\n\nexport const getFlow = async (\n client: FlowApiClient,\n env: string,\n flow: string,\n): Promise<Either<FlowApiError, FlowDetail>> => {\n const result = await client.request<RawFlow>(\n \"GET\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}`,\n )\n return result.map((raw) => {\n const definition = raw.properties?.definition\n return {\n ...mapSummary(raw),\n definition,\n connectionReferences: raw.properties?.connectionReferences,\n triggers: namesOf(definition?.triggers),\n actions: namesOf(definition?.actions),\n }\n })\n}\n\n// ── Write ─────────────────────────────────────────────────────────────\n\nexport const enableFlow = (client: FlowApiClient, env: string, flow: string): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\"POST\", `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/start`)\n\nexport const disableFlow = (client: FlowApiClient, env: string, flow: string): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\"POST\", `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/stop`)\n"],"mappings":"AAiCA,MAAM,EAAW,GAA6C,GAAS,QAAU,GAAS,OAAS,GAAS,YAEtG,EAAW,GAAgD,EAAS,OAAO,KAAK,CAAM,EAAI,CAAC,EAE3F,EAAc,GAA8B,CAChD,IAAM,EAAI,EAAI,YAAc,CAAC,EAC7B,MAAO,CACL,KAAM,EAAI,MAAQ,GAClB,YAAa,EAAE,aAAe,EAAI,MAAQ,GAC1C,MAAO,EAAE,OAAS,UAClB,YAAa,EAAE,aAAe,GAC9B,iBAAkB,EAAE,kBAAoB,GACxC,MAAO,EAAQ,EAAE,OAAO,CAC1B,CACF,EAEa,EAAY,MACvB,EACA,EACA,KAGO,MADc,EAAO,QAA+B,MAAO,iBAAiB,mBAAmB,CAAG,EAAE,OAAO,GACpG,IAAK,GAAa,CAC9B,IAAM,GAAS,EAAS,OAAS,CAAC,GAAG,IAAI,CAAU,EAC7C,EAAQ,GAAQ,MACtB,OAAO,EAAQ,EAAM,OAAQ,GAAM,EAAE,QAAU,CAAK,EAAI,CAC1D,CAAC,EAGU,EAAU,MACrB,EACA,EACA,KAMO,MAJc,EAAO,QAC1B,MACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,GAC3E,GACc,IAAK,GAAQ,CACzB,IAAM,EAAa,EAAI,YAAY,WACnC,MAAO,CACL,GAAG,EAAW,CAAG,EACjB,aACA,qBAAsB,EAAI,YAAY,qBACtC,SAAU,EAAQ,GAAY,QAAQ,EACtC,QAAS,EAAQ,GAAY,OAAO,CACtC,CACF,CAAC,EAKU,GAAc,EAAuB,EAAa,IAC7D,EAAO,QAAiB,OAAQ,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,OAAO,EAEvG,GAAe,EAAuB,EAAa,IAC9D,EAAO,QAAiB,OAAQ,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,MAAM"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>{let t=e.properties?.principal??{};return{principalId:t.id??e.name??``,principalType:t.type??`Unknown`,roleName:e.properties?.roleName??`Unknown`,principalDisplayName:t.displayName??t.email}},t=async(t,n,r)=>(await t.request(`GET`,`/environments/${encodeURIComponent(n)}/flows/${encodeURIComponent(r)}/permissions`)).map(t=>(t.value??[]).map(e)),n=(e,t,n,r,i)=>e.request(`PUT`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/permissions/${encodeURIComponent(r)}`,{body:{properties:{principal:{id:r,type:`User`},roleName:i}}}),r=(e,t,n,r)=>e.request(`DELETE`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/permissions/${encodeURIComponent(r)}`);export{n as addFlowOwner,t as listFlowOwners,r as removeFlowOwner};
|
|
2
|
+
//# sourceMappingURL=owners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owners.js","names":[],"sources":["../../../src/backend/flow-api/owners.ts"],"sourcesContent":["/**\n * Flow ownership/permissions. `GET /environments/{env}/flows/{flow}/permissions`.\n */\n\nimport type { Either } from \"functype\"\n\nimport type { FlowApiError } from \"../../errors.js\"\nimport type { FlowOwner } from \"../types.js\"\nimport type { FlowApiClient } from \"./client.js\"\n\ntype RawPrincipal = { id?: string; type?: string; displayName?: string; email?: string; tenantId?: string }\n\ntype RawPermissionProperties = { principal?: RawPrincipal; roleName?: string }\n\ntype RawPermission = { name?: string; properties?: RawPermissionProperties }\n\ntype ListEnvelope<T> = { value?: T[] }\n\nconst mapOwner = (raw: RawPermission): FlowOwner => {\n const principal = raw.properties?.principal ?? {}\n return {\n principalId: principal.id ?? raw.name ?? \"\",\n principalType: principal.type ?? \"Unknown\",\n roleName: raw.properties?.roleName ?? \"Unknown\",\n principalDisplayName: principal.displayName ?? principal.email,\n }\n}\n\nexport const listFlowOwners = async (\n client: FlowApiClient,\n env: string,\n flow: string,\n): Promise<Either<FlowApiError, FlowOwner[]>> => {\n const result = await client.request<ListEnvelope<RawPermission>>(\n \"GET\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/permissions`,\n )\n return result.map((envelope) => (envelope.value ?? []).map(mapOwner))\n}\n\n// ── Write ─────────────────────────────────────────────────────────────\n\nexport type OwnerRole = \"CanEdit\" | \"CanView\"\n\n/**\n * Grant a principal a role on a flow. The exact PUT permissions body shape for this\n * unofficial endpoint is not firmly documented — verify against the portal's network tab\n * and update docs/api-notes.md if the API rejects this payload.\n */\nexport const addFlowOwner = (\n client: FlowApiClient,\n env: string,\n flow: string,\n principalId: string,\n roleName: OwnerRole,\n): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\n \"PUT\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/permissions/${encodeURIComponent(\n principalId,\n )}`,\n { body: { properties: { principal: { id: principalId, type: \"User\" }, roleName } } },\n )\n\nexport const removeFlowOwner = (\n client: FlowApiClient,\n env: string,\n flow: string,\n principalId: string,\n): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\n \"DELETE\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/permissions/${encodeURIComponent(\n principalId,\n )}`,\n )\n"],"mappings":"AAkBA,MAAM,EAAY,GAAkC,CAClD,IAAM,EAAY,EAAI,YAAY,WAAa,CAAC,EAChD,MAAO,CACL,YAAa,EAAU,IAAM,EAAI,MAAQ,GACzC,cAAe,EAAU,MAAQ,UACjC,SAAU,EAAI,YAAY,UAAY,UACtC,qBAAsB,EAAU,aAAe,EAAU,KAC3D,CACF,EAEa,EAAiB,MAC5B,EACA,EACA,KAMO,MAJc,EAAO,QAC1B,MACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,aAC7E,GACc,IAAK,IAAc,EAAS,OAAS,CAAC,GAAG,IAAI,CAAQ,CAAC,EAYzD,GACX,EACA,EACA,EACA,EACA,IAEA,EAAO,QACL,MACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,eAAe,mBACxF,CACF,IACA,CAAE,KAAM,CAAE,WAAY,CAAE,UAAW,CAAE,GAAI,EAAa,KAAM,MAAO,EAAG,UAAS,CAAE,CAAE,CACrF,EAEW,GACX,EACA,EACA,EACA,IAEA,EAAO,QACL,SACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,eAAe,mBACxF,CACF,GACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=(e,t)=>{if(!e||!t)return;let n=Date.parse(e),r=Date.parse(t);return Number.isFinite(n)&&Number.isFinite(r)?r-n:void 0},t=t=>{let n=t.properties??{};return{name:t.name??``,status:n.status??`Unknown`,startTime:n.startTime??``,endTime:n.endTime,durationMs:e(n.startTime,n.endTime),triggerName:n.trigger?.name,error:n.error?.message}},n=async(e,n,r,i={})=>{let a=Math.min(Math.max(i.top??20,1),100);return(await e.request(`GET`,`/environments/${encodeURIComponent(n)}/flows/${encodeURIComponent(r)}/runs`,{query:{$top:a}})).map(e=>{let n=(e.value??[]).map(t),r=i.status;return r?n.filter(e=>e.status.toLowerCase()===r.toLowerCase()):n})},r=async(e,n,r,i)=>(await e.request(`GET`,`/environments/${encodeURIComponent(n)}/flows/${encodeURIComponent(r)}/runs/${encodeURIComponent(i)}`)).map(e=>({...t(e),errorCode:e.properties?.error?.code??e.properties?.code,raw:e.properties})),i=(e,t,n,r)=>e.request(`POST`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/runs/${encodeURIComponent(r)}/cancel`),a=(e,t,n,r,i)=>e.request(`POST`,`/environments/${encodeURIComponent(t)}/flows/${encodeURIComponent(n)}/triggers/${encodeURIComponent(r)}/histories/${encodeURIComponent(i)}/resubmit`);export{i as cancelRun,r as getRun,n as listRuns,a as resubmitRun};
|
|
2
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","names":[],"sources":["../../../src/backend/flow-api/runs.ts"],"sourcesContent":["/**\n * Run listing + detail. `GET /environments/{env}/flows/{flow}/runs[/{run}]`.\n *\n * get_flow_run is the debugging workhorse: it surfaces status, the failing trigger/error,\n * and keeps the original `properties` blob in `raw` so per-action detail (when the API\n * includes it) is available to the agent.\n */\n\nimport type { Either } from \"functype\"\n\nimport type { FlowApiError } from \"../../errors.js\"\nimport type { RunDetail, RunSummary } from \"../types.js\"\nimport type { FlowApiClient } from \"./client.js\"\n\ntype RawTrigger = { name?: string; status?: string; code?: string }\ntype RawRunError = { code?: string; message?: string }\n\ntype RawRunProperties = {\n status?: string\n startTime?: string\n endTime?: string\n trigger?: RawTrigger\n error?: RawRunError\n code?: string\n}\n\ntype RawRun = { name?: string; properties?: RawRunProperties }\n\ntype ListEnvelope<T> = { value?: T[] }\n\nexport type RunListOpts = { top?: number; status?: string }\n\nconst durationMs = (start?: string, end?: string): number | undefined => {\n if (!start || !end) return undefined\n const s = Date.parse(start)\n const e = Date.parse(end)\n return Number.isFinite(s) && Number.isFinite(e) ? e - s : undefined\n}\n\nconst mapRunSummary = (raw: RawRun): RunSummary => {\n const p = raw.properties ?? {}\n return {\n name: raw.name ?? \"\",\n status: p.status ?? \"Unknown\",\n startTime: p.startTime ?? \"\",\n endTime: p.endTime,\n durationMs: durationMs(p.startTime, p.endTime),\n triggerName: p.trigger?.name,\n error: p.error?.message,\n }\n}\n\nexport const listRuns = async (\n client: FlowApiClient,\n env: string,\n flow: string,\n opts: RunListOpts = {},\n): Promise<Either<FlowApiError, RunSummary[]>> => {\n const top = Math.min(Math.max(opts.top ?? 20, 1), 100)\n const result = await client.request<ListEnvelope<RawRun>>(\n \"GET\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/runs`,\n { query: { $top: top } },\n )\n return result.map((envelope) => {\n const runs = (envelope.value ?? []).map(mapRunSummary)\n const status = opts.status\n return status ? runs.filter((r) => r.status.toLowerCase() === status.toLowerCase()) : runs\n })\n}\n\nexport const getRun = async (\n client: FlowApiClient,\n env: string,\n flow: string,\n run: string,\n): Promise<Either<FlowApiError, RunDetail>> => {\n const result = await client.request<RawRun>(\n \"GET\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/runs/${encodeURIComponent(run)}`,\n )\n return result.map((raw) => ({\n ...mapRunSummary(raw),\n errorCode: raw.properties?.error?.code ?? raw.properties?.code,\n raw: raw.properties,\n }))\n}\n\n// ── Write ─────────────────────────────────────────────────────────────\n\nexport const cancelRun = (\n client: FlowApiClient,\n env: string,\n flow: string,\n run: string,\n): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\n \"POST\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/runs/${encodeURIComponent(run)}/cancel`,\n )\n\nexport const resubmitRun = (\n client: FlowApiClient,\n env: string,\n flow: string,\n trigger: string,\n run: string,\n): Promise<Either<FlowApiError, unknown>> =>\n client.request<unknown>(\n \"POST\",\n `/environments/${encodeURIComponent(env)}/flows/${encodeURIComponent(flow)}/triggers/${encodeURIComponent(\n trigger,\n )}/histories/${encodeURIComponent(run)}/resubmit`,\n )\n"],"mappings":"AAgCA,MAAM,GAAc,EAAgB,IAAqC,CACvE,GAAI,CAAC,GAAS,CAAC,EAAK,OACpB,IAAM,EAAI,KAAK,MAAM,CAAK,EACpB,EAAI,KAAK,MAAM,CAAG,EACxB,OAAO,OAAO,SAAS,CAAC,GAAK,OAAO,SAAS,CAAC,EAAI,EAAI,EAAI,IAAA,EAC5D,EAEM,EAAiB,GAA4B,CACjD,IAAM,EAAI,EAAI,YAAc,CAAC,EAC7B,MAAO,CACL,KAAM,EAAI,MAAQ,GAClB,OAAQ,EAAE,QAAU,UACpB,UAAW,EAAE,WAAa,GAC1B,QAAS,EAAE,QACX,WAAY,EAAW,EAAE,UAAW,EAAE,OAAO,EAC7C,YAAa,EAAE,SAAS,KACxB,MAAO,EAAE,OAAO,OAClB,CACF,EAEa,EAAW,MACtB,EACA,EACA,EACA,EAAoB,CAAC,IAC2B,CAChD,IAAM,EAAM,KAAK,IAAI,KAAK,IAAI,EAAK,KAAO,GAAI,CAAC,EAAG,GAAG,EAMrD,OAAO,MALc,EAAO,QAC1B,MACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,OAC3E,CAAE,MAAO,CAAE,KAAM,CAAI,CAAE,CACzB,GACc,IAAK,GAAa,CAC9B,IAAM,GAAQ,EAAS,OAAS,CAAC,GAAG,IAAI,CAAa,EAC/C,EAAS,EAAK,OACpB,OAAO,EAAS,EAAK,OAAQ,GAAM,EAAE,OAAO,YAAY,IAAM,EAAO,YAAY,CAAC,EAAI,CACxF,CAAC,CACH,EAEa,EAAS,MACpB,EACA,EACA,EACA,KAMO,MAJc,EAAO,QAC1B,MACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,QAAQ,mBAAmB,CAAG,GAC3G,GACc,IAAK,IAAS,CAC1B,GAAG,EAAc,CAAG,EACpB,UAAW,EAAI,YAAY,OAAO,MAAQ,EAAI,YAAY,KAC1D,IAAK,EAAI,UACX,EAAE,EAKS,GACX,EACA,EACA,EACA,IAEA,EAAO,QACL,OACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,QAAQ,mBAAmB,CAAG,EAAE,QAC7G,EAEW,GACX,EACA,EACA,EACA,EACA,IAEA,EAAO,QACL,OACA,iBAAiB,mBAAmB,CAAG,EAAE,SAAS,mBAAmB,CAAI,EAAE,YAAY,mBACrF,CACF,EAAE,aAAa,mBAAmB,CAAG,EAAE,UACzC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { a as FlowApiError } from "../errors-BsWrc7px.js";
|
|
2
|
+
import { n as TokenProvider } from "../token-manager-BfUM20Tf.js";
|
|
3
|
+
import { a as FlowSummary, c as RunSummary, i as FlowOwner, n as Environment, o as RunDetail, r as FlowDetail, t as Connection } from "../types-DRNRhdJl.js";
|
|
4
|
+
import { t as FlowFilter } from "../flows-B0jBc1wf.js";
|
|
5
|
+
import { t as OwnerRole } from "../owners-CdI2c9rS.js";
|
|
6
|
+
import { t as RunListOpts } from "../runs-DdpaFLpn.js";
|
|
7
|
+
import { Either } from "functype";
|
|
8
|
+
|
|
9
|
+
//#region src/backend/index.d.ts
|
|
10
|
+
type FlowBackend = {
|
|
11
|
+
listEnvironments: () => Promise<Either<FlowApiError, Environment[]>>; /** Resolve the user's default environment id (isDefault, else first). */
|
|
12
|
+
resolveDefaultEnvironment: () => Promise<Either<FlowApiError, string>>;
|
|
13
|
+
listFlows: (env: string, filter?: FlowFilter) => Promise<Either<FlowApiError, FlowSummary[]>>;
|
|
14
|
+
getFlow: (env: string, flow: string) => Promise<Either<FlowApiError, FlowDetail>>;
|
|
15
|
+
listRuns: (env: string, flow: string, opts?: RunListOpts) => Promise<Either<FlowApiError, RunSummary[]>>;
|
|
16
|
+
getRun: (env: string, flow: string, run: string) => Promise<Either<FlowApiError, RunDetail>>;
|
|
17
|
+
listConnections: (env: string) => Promise<Either<FlowApiError, Connection[]>>;
|
|
18
|
+
listFlowOwners: (env: string, flow: string) => Promise<Either<FlowApiError, FlowOwner[]>>;
|
|
19
|
+
enableFlow: (env: string, flow: string) => Promise<Either<FlowApiError, unknown>>;
|
|
20
|
+
disableFlow: (env: string, flow: string) => Promise<Either<FlowApiError, unknown>>;
|
|
21
|
+
cancelFlowRun: (env: string, flow: string, run: string) => Promise<Either<FlowApiError, unknown>>;
|
|
22
|
+
resubmitFlowRun: (env: string, flow: string, trigger: string, run: string) => Promise<Either<FlowApiError, unknown>>;
|
|
23
|
+
addFlowOwner: (env: string, flow: string, principalId: string, roleName: OwnerRole) => Promise<Either<FlowApiError, unknown>>;
|
|
24
|
+
removeFlowOwner: (env: string, flow: string, principalId: string) => Promise<Either<FlowApiError, unknown>>;
|
|
25
|
+
};
|
|
26
|
+
declare const createFlowApiBackend: (deps: {
|
|
27
|
+
tokenProvider: TokenProvider;
|
|
28
|
+
}) => FlowBackend;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { FlowBackend, type FlowFilter, type OwnerRole, type RunListOpts, createFlowApiBackend };
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{leftFlowApi as e}from"../errors.js";import{createFlowApiClient as t}from"./flow-api/client.js";import{listConnections as n}from"./flow-api/connections.js";import{listEnvironments as r}from"./flow-api/environments.js";import{disableFlow as i,enableFlow as a,getFlow as o,listFlows as s}from"./flow-api/flows.js";import{addFlowOwner as c,listFlowOwners as l,removeFlowOwner as u}from"./flow-api/owners.js";import{cancelRun as d,getRun as f,listRuns as p,resubmitRun as m}from"./flow-api/runs.js";import{Right as h}from"functype";const g=g=>{let _=t({tokenProvider:g.tokenProvider});return{listEnvironments:()=>r(_),resolveDefaultEnvironment:async()=>(await r(_)).flatMap(t=>{let n=t.find(e=>e.isDefault)??t[0];return n?h(n.id):e(`not_found`,`no environments available to resolve a default`)}),listFlows:(e,t)=>s(_,e,t),getFlow:(e,t)=>o(_,e,t),listRuns:(e,t,n)=>p(_,e,t,n),getRun:(e,t,n)=>f(_,e,t,n),listConnections:e=>n(_,e),listFlowOwners:(e,t)=>l(_,e,t),enableFlow:(e,t)=>a(_,e,t),disableFlow:(e,t)=>i(_,e,t),cancelFlowRun:(e,t,n)=>d(_,e,t,n),resubmitFlowRun:(e,t,n,r)=>m(_,e,t,n,r),addFlowOwner:(e,t,n,r)=>c(_,e,t,n,r),removeFlowOwner:(e,t,n)=>u(_,e,t,n)}};export{g as createFlowApiBackend};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/backend/index.ts"],"sourcesContent":["/**\n * The {@link FlowBackend} seam — the contract every tool depends on, independent of which\n * Microsoft surface answers it.\n *\n * Named `FlowBackend` (not `BackendAdapter`) deliberately: SomaMCP exports its own\n * `BackendAdapter` for the MCP *transport* layer, which is a different concern.\n *\n * v1 ships only {@link createFlowApiBackend} (api.flow.microsoft.com). A future Dataverse\n * backend (see ./dataverse/README.md) would implement the same interface from\n * `<org>.dynamics.com/api/data/v9.2/workflows`.\n */\n\nimport { type Either, Right } from \"functype\"\n\nimport type { TokenProvider } from \"../auth/token-manager.js\"\nimport { type FlowApiError, leftFlowApi } from \"../errors.js\"\nimport { createFlowApiClient } from \"./flow-api/client.js\"\nimport { listConnections } from \"./flow-api/connections.js\"\nimport { listEnvironments } from \"./flow-api/environments.js\"\nimport { disableFlow, enableFlow, type FlowFilter, getFlow, listFlows } from \"./flow-api/flows.js\"\nimport { addFlowOwner, listFlowOwners, type OwnerRole, removeFlowOwner } from \"./flow-api/owners.js\"\nimport { cancelRun, getRun, listRuns, resubmitRun, type RunListOpts } from \"./flow-api/runs.js\"\nimport type { Connection, Environment, FlowDetail, FlowOwner, FlowSummary, RunDetail, RunSummary } from \"./types.js\"\n\nexport type { FlowFilter } from \"./flow-api/flows.js\"\nexport type { OwnerRole } from \"./flow-api/owners.js\"\nexport type { RunListOpts } from \"./flow-api/runs.js\"\n\nexport type FlowBackend = {\n listEnvironments: () => Promise<Either<FlowApiError, Environment[]>>\n /** Resolve the user's default environment id (isDefault, else first). */\n resolveDefaultEnvironment: () => Promise<Either<FlowApiError, string>>\n listFlows: (env: string, filter?: FlowFilter) => Promise<Either<FlowApiError, FlowSummary[]>>\n getFlow: (env: string, flow: string) => Promise<Either<FlowApiError, FlowDetail>>\n listRuns: (env: string, flow: string, opts?: RunListOpts) => Promise<Either<FlowApiError, RunSummary[]>>\n getRun: (env: string, flow: string, run: string) => Promise<Either<FlowApiError, RunDetail>>\n listConnections: (env: string) => Promise<Either<FlowApiError, Connection[]>>\n listFlowOwners: (env: string, flow: string) => Promise<Either<FlowApiError, FlowOwner[]>>\n // Write — only invoked when ENABLE_WRITE_OPS=true (gated at the tool layer).\n enableFlow: (env: string, flow: string) => Promise<Either<FlowApiError, unknown>>\n disableFlow: (env: string, flow: string) => Promise<Either<FlowApiError, unknown>>\n cancelFlowRun: (env: string, flow: string, run: string) => Promise<Either<FlowApiError, unknown>>\n resubmitFlowRun: (env: string, flow: string, trigger: string, run: string) => Promise<Either<FlowApiError, unknown>>\n addFlowOwner: (\n env: string,\n flow: string,\n principalId: string,\n roleName: OwnerRole,\n ) => Promise<Either<FlowApiError, unknown>>\n removeFlowOwner: (env: string, flow: string, principalId: string) => Promise<Either<FlowApiError, unknown>>\n}\n\nexport const createFlowApiBackend = (deps: { tokenProvider: TokenProvider }): FlowBackend => {\n const client = createFlowApiClient({ tokenProvider: deps.tokenProvider })\n\n return {\n listEnvironments: () => listEnvironments(client),\n resolveDefaultEnvironment: async () => {\n const envs = await listEnvironments(client)\n return envs.flatMap((list) => {\n const def = list.find((e) => e.isDefault) ?? list[0]\n return def ? Right(def.id) : leftFlowApi<string>(\"not_found\", \"no environments available to resolve a default\")\n })\n },\n listFlows: (env, filter) => listFlows(client, env, filter),\n getFlow: (env, flow) => getFlow(client, env, flow),\n listRuns: (env, flow, opts) => listRuns(client, env, flow, opts),\n getRun: (env, flow, run) => getRun(client, env, flow, run),\n listConnections: (env) => listConnections(client, env),\n listFlowOwners: (env, flow) => listFlowOwners(client, env, flow),\n enableFlow: (env, flow) => enableFlow(client, env, flow),\n disableFlow: (env, flow) => disableFlow(client, env, flow),\n cancelFlowRun: (env, flow, run) => cancelRun(client, env, flow, run),\n resubmitFlowRun: (env, flow, trigger, run) => resubmitRun(client, env, flow, trigger, run),\n addFlowOwner: (env, flow, principalId, roleName) => addFlowOwner(client, env, flow, principalId, roleName),\n removeFlowOwner: (env, flow, principalId) => removeFlowOwner(client, env, flow, principalId),\n }\n}\n"],"mappings":"shBAoDA,MAAa,EAAwB,GAAwD,CAC3F,IAAM,EAAS,EAAoB,CAAE,cAAe,EAAK,aAAc,CAAC,EAExE,MAAO,CACL,qBAAwB,EAAiB,CAAM,EAC/C,0BAA2B,UAElB,MADY,EAAiB,CAAM,GAC9B,QAAS,GAAS,CAC5B,IAAM,EAAM,EAAK,KAAM,GAAM,EAAE,SAAS,GAAK,EAAK,GAClD,OAAO,EAAM,EAAM,EAAI,EAAE,EAAI,EAAoB,YAAa,gDAAgD,CAChH,CAAC,EAEH,WAAY,EAAK,IAAW,EAAU,EAAQ,EAAK,CAAM,EACzD,SAAU,EAAK,IAAS,EAAQ,EAAQ,EAAK,CAAI,EACjD,UAAW,EAAK,EAAM,IAAS,EAAS,EAAQ,EAAK,EAAM,CAAI,EAC/D,QAAS,EAAK,EAAM,IAAQ,EAAO,EAAQ,EAAK,EAAM,CAAG,EACzD,gBAAkB,GAAQ,EAAgB,EAAQ,CAAG,EACrD,gBAAiB,EAAK,IAAS,EAAe,EAAQ,EAAK,CAAI,EAC/D,YAAa,EAAK,IAAS,EAAW,EAAQ,EAAK,CAAI,EACvD,aAAc,EAAK,IAAS,EAAY,EAAQ,EAAK,CAAI,EACzD,eAAgB,EAAK,EAAM,IAAQ,EAAU,EAAQ,EAAK,EAAM,CAAG,EACnE,iBAAkB,EAAK,EAAM,EAAS,IAAQ,EAAY,EAAQ,EAAK,EAAM,EAAS,CAAG,EACzF,cAAe,EAAK,EAAM,EAAa,IAAa,EAAa,EAAQ,EAAK,EAAM,EAAa,CAAQ,EACzG,iBAAkB,EAAK,EAAM,IAAgB,EAAgB,EAAQ,EAAK,EAAM,CAAW,CAC7F,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as FlowSummary, c as RunSummary, i as FlowOwner, n as Environment, o as RunDetail, r as FlowDetail, s as RunStatus, t as Connection } from "../types-DRNRhdJl.js";
|
|
2
|
+
export { Connection, Environment, FlowDetail, FlowOwner, FlowSummary, RunDetail, RunStatus, RunSummary };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{loadConfig as e}from"./config.js";import{createPowerAutomateServer as t}from"./index.js";import"dotenv/config";(async()=>{await e().fold(async e=>{console.error(`[power-automate] configuration error: ${e.message}`),process.exit(1)},async e=>{let n=process.argv.includes(`--stdio`)?`stdio`:e.transport,r=t({...e,transport:n});n===`http`?(await r.start({transportType:`httpStream`,httpStream:{port:e.port,endpoint:e.endpoint}}),console.error(`[power-automate] listening on http://localhost:${e.port}${e.endpoint}`)):(await r.start({transportType:`stdio`}),console.error(`[power-automate] running on stdio`))})})();export{};
|
|
3
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI entry point. Loads config from the environment (.env supported via dotenv), builds the\n * server, and starts the chosen transport.\n *\n * power-automate-mcp-server # transport from TRANSPORT env (default stdio)\n * power-automate-mcp-server --stdio # force stdio regardless of TRANSPORT\n *\n * On the interactive auth path the device-code prompt is written to stderr, keeping stdout\n * clean for the stdio MCP protocol.\n */\n\nimport \"dotenv/config\"\n\nimport { loadConfig } from \"./config.js\"\nimport { createPowerAutomateServer } from \"./index.js\"\n\nconst main = async (): Promise<void> => {\n await loadConfig().fold(\n async (err): Promise<void> => {\n console.error(`[power-automate] configuration error: ${err.message}`)\n process.exit(1)\n },\n async (config): Promise<void> => {\n const transport = process.argv.includes(\"--stdio\") ? \"stdio\" : config.transport\n const server = createPowerAutomateServer({ ...config, transport })\n\n if (transport === \"http\") {\n await server.start({\n transportType: \"httpStream\",\n httpStream: { port: config.port, endpoint: config.endpoint },\n })\n console.error(`[power-automate] listening on http://localhost:${config.port}${config.endpoint}`)\n } else {\n await server.start({ transportType: \"stdio\" })\n console.error(\"[power-automate] running on stdio\")\n }\n },\n )\n}\n\nvoid main()\n"],"mappings":";uHAyCK,SAxBmC,CACtC,MAAM,EAAW,EAAE,KACjB,KAAO,IAAuB,CAC5B,QAAQ,MAAM,yCAAyC,EAAI,SAAS,EACpE,QAAQ,KAAK,CAAC,CAChB,EACA,KAAO,IAA0B,CAC/B,IAAM,EAAY,QAAQ,KAAK,SAAS,SAAS,EAAI,QAAU,EAAO,UAChE,EAAS,EAA0B,CAAE,GAAG,EAAQ,WAAU,CAAC,EAE7D,IAAc,QAChB,MAAM,EAAO,MAAM,CACjB,cAAe,aACf,WAAY,CAAE,KAAM,EAAO,KAAM,SAAU,EAAO,QAAS,CAC7D,CAAC,EACD,QAAQ,MAAM,kDAAkD,EAAO,OAAO,EAAO,UAAU,IAE/F,MAAM,EAAO,MAAM,CAAE,cAAe,OAAQ,CAAC,EAC7C,QAAQ,MAAM,mCAAmC,EAErD,CACF,CACF,GAEU"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as FlowApiError } from "./errors-BsWrc7px.js";
|
|
2
|
+
import { n as TokenProvider } from "./token-manager-BfUM20Tf.js";
|
|
3
|
+
import { Either } from "functype";
|
|
4
|
+
|
|
5
|
+
//#region src/backend/flow-api/client.d.ts
|
|
6
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
7
|
+
type RequestOptions = {
|
|
8
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
9
|
+
body?: unknown; /** Override the default 2016-11-01 api-version for an endpoint that needs a newer one. */
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
};
|
|
12
|
+
type FlowApiClient = {
|
|
13
|
+
request: <T>(method: HttpMethod, path: string, options?: RequestOptions) => Promise<Either<FlowApiError, T>>;
|
|
14
|
+
};
|
|
15
|
+
declare const createFlowApiClient: (deps: {
|
|
16
|
+
tokenProvider: TokenProvider;
|
|
17
|
+
}) => FlowApiClient;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { createFlowApiClient as i, HttpMethod as n, RequestOptions as r, FlowApiClient as t };
|
|
20
|
+
//# sourceMappingURL=client-DCWt6ssY.d.ts.map
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { i as ConfigError } from "./errors-BsWrc7px.js";
|
|
2
|
+
import { i as ServerConfig } from "./types-HGbUI9Dc.js";
|
|
3
|
+
import { Either } from "functype";
|
|
4
|
+
|
|
5
|
+
//#region src/config.d.ts
|
|
6
|
+
declare const loadConfig: (env?: NodeJS.ProcessEnv) => Either<ConfigError, ServerConfig>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { loadConfig };
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./auth/types.js";import{configError as e}from"./errors.js";import{homedir as t}from"node:os";import{resolve as n}from"node:path";import{Left as r,Right as i}from"functype";const a=e=>e===`~`?t():e.startsWith(`~/`)?n(t(),e.slice(2)):e,o=t=>!t||t===`interactive`?i(`interactive`):t===`clientCredentials`?i(`clientCredentials`):r(e(`Invalid AZURE_AUTH_MODE: "${t}". Must be "interactive" or "clientCredentials".`)),s=t=>!t||t===`stdio`?i(`stdio`):t===`http`?i(`http`):r(e(`Invalid TRANSPORT: "${t}". Must be "stdio" or "http".`)),c=e=>(e??`console,file`).split(`,`).map(e=>e.trim()).filter(e=>e===`console`||e===`file`),l=e=>{let t=e?.trim()||`/mcp`;return t.startsWith(`/`)?t:`/${t}`},u=e=>{let t=Number.parseInt(e??`3333`,10);return Number.isFinite(t)?t:3333},d=(t=process.env)=>{let n=t.AZURE_CLIENT_ID?.trim();return n?o(t.AZURE_AUTH_MODE).flatMap(o=>s(t.TRANSPORT).flatMap(s=>{let d=t.AZURE_TENANT_ID?.trim()||`common`,f=t.AZURE_CLIENT_SECRET?.trim()||void 0;if(o===`clientCredentials`){if(!f)return r(e(`AZURE_AUTH_MODE=clientCredentials requires AZURE_CLIENT_SECRET to be set.`));if(d===`common`)return r(e(`AZURE_AUTH_MODE=clientCredentials requires a specific AZURE_TENANT_ID (not "common").`))}let p=t.FLOW_RESOURCE?.trim()||`https://service.flow.microsoft.com/`;return i({clientId:n,tenantId:d,authMode:o,clientSecret:f,flowResource:p,flowScopes:t.FLOW_SCOPES?.split(`,`).map(e=>e.trim()).filter(Boolean)??[`${p}.default`],tokenCachePath:a(t.TOKEN_CACHE_PATH?.trim()||`~/.cache/power-automate-mcp/token.json`),transport:s,port:u(t.PORT),endpoint:l(t.ENDPOINT),mcpApiKey:t.MCP_API_KEY?.trim()||void 0,enableWriteOps:t.ENABLE_WRITE_OPS===`true`,defaultEnvironment:t.DEFAULT_ENVIRONMENT?.trim()||void 0,telemetry:c(t.TELEMETRY),telemetryFilePath:t.TELEMETRY_FILE?.trim()||`./logs/events.ndjson`,logLevel:t.LOG_LEVEL?.trim()||`info`,feedbackRepo:t.FEEDBACK_GITHUB_REPO?.trim()||`sapientsai/power-automate-mcp-server`})})):r(e(`AZURE_CLIENT_ID is required. Register a multi-tenant public client app and set AZURE_CLIENT_ID. See README "App registration".`))};export{d as loadConfig};
|
|
2
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Environment-variable parsing into a validated {@link ServerConfig}.\n *\n * Returns `Either<ConfigError, ServerConfig>` so the caller (bin.ts) can fail loudly with\n * an actionable message. Every variable either has a sane default or is reported as missing.\n */\n\nimport { homedir } from \"node:os\"\nimport { resolve } from \"node:path\"\n\nimport { type Either, Left, Right } from \"functype\"\n\nimport {\n type AuthMode,\n FLOW_RESOURCE,\n type ServerConfig,\n type TelemetrySink,\n type TransportKind,\n} from \"./auth/types.js\"\nimport { type ConfigError, configError } from \"./errors.js\"\n\nconst expandHome = (p: string): string => {\n if (p === \"~\") return homedir()\n if (p.startsWith(\"~/\")) return resolve(homedir(), p.slice(2))\n return p\n}\n\nconst parseAuthMode = (value: string | undefined): Either<ConfigError, AuthMode> => {\n if (!value || value === \"interactive\") return Right(\"interactive\")\n if (value === \"clientCredentials\") return Right(\"clientCredentials\")\n return Left(configError(`Invalid AZURE_AUTH_MODE: \"${value}\". Must be \"interactive\" or \"clientCredentials\".`))\n}\n\nconst parseTransport = (value: string | undefined): Either<ConfigError, TransportKind> => {\n if (!value || value === \"stdio\") return Right(\"stdio\")\n if (value === \"http\") return Right(\"http\")\n return Left(configError(`Invalid TRANSPORT: \"${value}\". Must be \"stdio\" or \"http\".`))\n}\n\nconst parseTelemetry = (value: string | undefined): ReadonlyArray<TelemetrySink> =>\n (value ?? \"console,file\")\n .split(\",\")\n .map((s) => s.trim())\n .filter((s): s is TelemetrySink => s === \"console\" || s === \"file\")\n\nconst ensureEndpoint = (value: string | undefined): `/${string}` => {\n const e = value?.trim() || \"/mcp\"\n return (e.startsWith(\"/\") ? e : `/${e}`) as `/${string}`\n}\n\nconst parsePort = (value: string | undefined): number => {\n const n = Number.parseInt(value ?? \"3333\", 10)\n return Number.isFinite(n) ? n : 3333\n}\n\nexport const loadConfig = (env: NodeJS.ProcessEnv = process.env): Either<ConfigError, ServerConfig> => {\n const clientId = env.AZURE_CLIENT_ID?.trim()\n if (!clientId) {\n return Left(\n configError(\n 'AZURE_CLIENT_ID is required. Register a multi-tenant public client app and set AZURE_CLIENT_ID. See README \"App registration\".',\n ),\n )\n }\n\n return parseAuthMode(env.AZURE_AUTH_MODE).flatMap((authMode) =>\n parseTransport(env.TRANSPORT).flatMap((transport) => {\n const tenantId = env.AZURE_TENANT_ID?.trim() || \"common\"\n const clientSecret = env.AZURE_CLIENT_SECRET?.trim() || undefined\n\n if (authMode === \"clientCredentials\") {\n if (!clientSecret) {\n return Left(configError(\"AZURE_AUTH_MODE=clientCredentials requires AZURE_CLIENT_SECRET to be set.\"))\n }\n if (tenantId === \"common\") {\n return Left(\n configError('AZURE_AUTH_MODE=clientCredentials requires a specific AZURE_TENANT_ID (not \"common\").'),\n )\n }\n }\n\n const flowResource = env.FLOW_RESOURCE?.trim() || FLOW_RESOURCE\n const flowScopes = env.FLOW_SCOPES?.split(\",\")\n .map((s) => s.trim())\n .filter(Boolean) ?? [`${flowResource}.default`]\n\n const config: ServerConfig = {\n clientId,\n tenantId,\n authMode,\n clientSecret,\n flowResource,\n flowScopes,\n tokenCachePath: expandHome(env.TOKEN_CACHE_PATH?.trim() || \"~/.cache/power-automate-mcp/token.json\"),\n transport,\n port: parsePort(env.PORT),\n endpoint: ensureEndpoint(env.ENDPOINT),\n mcpApiKey: env.MCP_API_KEY?.trim() || undefined,\n enableWriteOps: env.ENABLE_WRITE_OPS === \"true\",\n defaultEnvironment: env.DEFAULT_ENVIRONMENT?.trim() || undefined,\n telemetry: parseTelemetry(env.TELEMETRY),\n telemetryFilePath: env.TELEMETRY_FILE?.trim() || \"./logs/events.ndjson\",\n logLevel: env.LOG_LEVEL?.trim() || \"info\",\n feedbackRepo: env.FEEDBACK_GITHUB_REPO?.trim() || \"sapientsai/power-automate-mcp-server\",\n }\n return Right(config)\n }),\n )\n}\n"],"mappings":"mLAqBA,MAAM,EAAc,GACd,IAAM,IAAY,EAAQ,EAC1B,EAAE,WAAW,IAAI,EAAU,EAAQ,EAAQ,EAAG,EAAE,MAAM,CAAC,CAAC,EACrD,EAGH,EAAiB,GACjB,CAAC,GAAS,IAAU,cAAsB,EAAM,aAAa,EAC7D,IAAU,oBAA4B,EAAM,mBAAmB,EAC5D,EAAK,EAAY,6BAA6B,EAAM,iDAAiD,CAAC,EAGzG,EAAkB,GAClB,CAAC,GAAS,IAAU,QAAgB,EAAM,OAAO,EACjD,IAAU,OAAe,EAAM,MAAM,EAClC,EAAK,EAAY,uBAAuB,EAAM,8BAA8B,CAAC,EAGhF,EAAkB,IACrB,GAAS,gBACP,MAAM,GAAG,EACT,IAAK,GAAM,EAAE,KAAK,CAAC,EACnB,OAAQ,GAA0B,IAAM,WAAa,IAAM,MAAM,EAEhE,EAAkB,GAA4C,CAClE,IAAM,EAAI,GAAO,KAAK,GAAK,OAC3B,OAAQ,EAAE,WAAW,GAAG,EAAI,EAAI,IAAI,GACtC,EAEM,EAAa,GAAsC,CACvD,IAAM,EAAI,OAAO,SAAS,GAAS,OAAQ,EAAE,EAC7C,OAAO,OAAO,SAAS,CAAC,EAAI,EAAI,IAClC,EAEa,GAAc,EAAyB,QAAQ,MAA2C,CACrG,IAAM,EAAW,EAAI,iBAAiB,KAAK,EAS3C,OARK,EAQE,EAAc,EAAI,eAAe,EAAE,QAAS,GACjD,EAAe,EAAI,SAAS,EAAE,QAAS,GAAc,CACnD,IAAM,EAAW,EAAI,iBAAiB,KAAK,GAAK,SAC1C,EAAe,EAAI,qBAAqB,KAAK,GAAK,IAAA,GAExD,GAAI,IAAa,oBAAqB,CACpC,GAAI,CAAC,EACH,OAAO,EAAK,EAAY,2EAA2E,CAAC,EAEtG,GAAI,IAAa,SACf,OAAO,EACL,EAAY,uFAAuF,CACrG,CAEJ,CAEA,IAAM,EAAe,EAAI,eAAe,KAAK,GAAA,sCAwB7C,OAAO,EAAM,CAlBX,WACA,WACA,WACA,eACA,eACA,WAViB,EAAI,aAAa,MAAM,GAAG,EAC1C,IAAK,GAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,GAAK,CAAC,GAAG,EAAa,SAAS,EAS9C,eAAgB,EAAW,EAAI,kBAAkB,KAAK,GAAK,wCAAwC,EACnG,YACA,KAAM,EAAU,EAAI,IAAI,EACxB,SAAU,EAAe,EAAI,QAAQ,EACrC,UAAW,EAAI,aAAa,KAAK,GAAK,IAAA,GACtC,eAAgB,EAAI,mBAAqB,OACzC,mBAAoB,EAAI,qBAAqB,KAAK,GAAK,IAAA,GACvD,UAAW,EAAe,EAAI,SAAS,EACvC,kBAAmB,EAAI,gBAAgB,KAAK,GAAK,uBACjD,SAAU,EAAI,WAAW,KAAK,GAAK,OACnC,aAAc,EAAI,sBAAsB,KAAK,GAAK,sCAElC,CAAC,CACrB,CAAC,CACH,EAjDS,EACL,EACE,gIACF,CACF,CA8CJ"}
|