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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/dist/auth/token-cache.d.ts +7 -0
  4. package/dist/auth/token-cache.js +2 -0
  5. package/dist/auth/token-cache.js.map +1 -0
  6. package/dist/auth/token-manager.d.ts +2 -0
  7. package/dist/auth/token-manager.js +2 -0
  8. package/dist/auth/token-manager.js.map +1 -0
  9. package/dist/auth/types.d.ts +2 -0
  10. package/dist/auth/types.js +2 -0
  11. package/dist/auth/types.js.map +1 -0
  12. package/dist/backend/flow-api/client.d.ts +3 -0
  13. package/dist/backend/flow-api/client.js +2 -0
  14. package/dist/backend/flow-api/client.js.map +1 -0
  15. package/dist/backend/flow-api/connections.d.ts +10 -0
  16. package/dist/backend/flow-api/connections.js +2 -0
  17. package/dist/backend/flow-api/connections.js.map +1 -0
  18. package/dist/backend/flow-api/environments.d.ts +10 -0
  19. package/dist/backend/flow-api/environments.js +2 -0
  20. package/dist/backend/flow-api/environments.js.map +1 -0
  21. package/dist/backend/flow-api/flows.d.ts +2 -0
  22. package/dist/backend/flow-api/flows.js +2 -0
  23. package/dist/backend/flow-api/flows.js.map +1 -0
  24. package/dist/backend/flow-api/owners.d.ts +2 -0
  25. package/dist/backend/flow-api/owners.js +2 -0
  26. package/dist/backend/flow-api/owners.js.map +1 -0
  27. package/dist/backend/flow-api/runs.d.ts +2 -0
  28. package/dist/backend/flow-api/runs.js +2 -0
  29. package/dist/backend/flow-api/runs.js.map +1 -0
  30. package/dist/backend/index.d.ts +31 -0
  31. package/dist/backend/index.js +2 -0
  32. package/dist/backend/index.js.map +1 -0
  33. package/dist/backend/types.d.ts +2 -0
  34. package/dist/backend/types.js +1 -0
  35. package/dist/bin.d.ts +1 -0
  36. package/dist/bin.js +3 -0
  37. package/dist/bin.js.map +1 -0
  38. package/dist/client-DCWt6ssY.d.ts +20 -0
  39. package/dist/config.d.ts +9 -0
  40. package/dist/config.js +2 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/errors-BsWrc7px.d.ts +42 -0
  43. package/dist/errors.d.ts +2 -0
  44. package/dist/errors.js +2 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/flows-B0jBc1wf.d.ts +16 -0
  47. package/dist/index.d.ts +12 -0
  48. package/dist/index.js +2 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/owners-CdI2c9rS.d.ts +18 -0
  51. package/dist/runs-DdpaFLpn.d.ts +17 -0
  52. package/dist/token-manager-BfUM20Tf.d.ts +15 -0
  53. package/dist/tools/connections.d.ts +9 -0
  54. package/dist/tools/connections.js +3 -0
  55. package/dist/tools/connections.js.map +1 -0
  56. package/dist/tools/environments.d.ts +8 -0
  57. package/dist/tools/environments.js +3 -0
  58. package/dist/tools/environments.js.map +1 -0
  59. package/dist/tools/flows.d.ts +10 -0
  60. package/dist/tools/flows.js +6 -0
  61. package/dist/tools/flows.js.map +1 -0
  62. package/dist/tools/owners.d.ts +10 -0
  63. package/dist/tools/owners.js +5 -0
  64. package/dist/tools/owners.js.map +1 -0
  65. package/dist/tools/runs.d.ts +10 -0
  66. package/dist/tools/runs.js +6 -0
  67. package/dist/tools/runs.js.map +1 -0
  68. package/dist/tools/shared.d.ts +29 -0
  69. package/dist/tools/shared.js +2 -0
  70. package/dist/tools/shared.js.map +1 -0
  71. package/dist/types-DRNRhdJl.d.ts +62 -0
  72. package/dist/types-HGbUI9Dc.d.ts +41 -0
  73. package/dist/version.d.ts +9 -0
  74. package/dist/version.js +2 -0
  75. package/dist/version.js.map +1 -0
  76. package/package.json +76 -0
@@ -0,0 +1,42 @@
1
+ import { Either } from "functype";
2
+
3
+ //#region src/errors.d.ts
4
+ type ConfigError = {
5
+ readonly _tag: "ConfigError";
6
+ readonly message: string;
7
+ };
8
+ type AuthErrorReason = "config" | "no_token" | "expired" | "device_code_failed" | "silent_failed" | "client_credentials_failed";
9
+ type AuthError = {
10
+ readonly _tag: "AuthError";
11
+ readonly message: string;
12
+ readonly reason: AuthErrorReason;
13
+ };
14
+ type FlowApiErrorKind = "auth_expired" | "not_found" | "forbidden" | "rate_limited" | "bad_request" | "server_error" | "network" | "unsupported_api_drift";
15
+ type FlowApiError = {
16
+ readonly _tag: "FlowApiError";
17
+ readonly kind: FlowApiErrorKind;
18
+ readonly message: string;
19
+ readonly status?: number;
20
+ readonly detail?: unknown;
21
+ };
22
+ type AppError = ConfigError | AuthError | FlowApiError;
23
+ declare const configError: (message: string) => ConfigError;
24
+ declare const authError: (message: string, reason?: AuthErrorReason) => AuthError;
25
+ declare const flowApiError: (kind: FlowApiErrorKind, message: string, opts?: {
26
+ status?: number;
27
+ detail?: unknown;
28
+ }) => FlowApiError;
29
+ declare const leftConfig: <T>(message: string) => Either<ConfigError, T>;
30
+ declare const leftAuth: <T>(message: string, reason?: AuthErrorReason) => Either<AuthError, T>;
31
+ declare const leftFlowApi: <T>(kind: FlowApiErrorKind, message: string, opts?: {
32
+ status?: number;
33
+ detail?: unknown;
34
+ }) => Either<FlowApiError, T>;
35
+ /**
36
+ * Convert an {@link AppError} into a throwable `Error` whose message both classifies well
37
+ * under SomaMCP and carries an actionable suggestion for the agent.
38
+ */
39
+ declare const appErrorToThrowable: (error: AppError) => Error;
40
+ //#endregion
41
+ export { FlowApiError as a, authError as c, leftAuth as d, leftConfig as f, ConfigError as i, configError as l, AuthError as n, FlowApiErrorKind as o, leftFlowApi as p, AuthErrorReason as r, appErrorToThrowable as s, AppError as t, flowApiError as u };
42
+ //# sourceMappingURL=errors-BsWrc7px.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { a as FlowApiError, c as authError, d as leftAuth, f as leftConfig, i as ConfigError, l as configError, n as AuthError, o as FlowApiErrorKind, p as leftFlowApi, r as AuthErrorReason, s as appErrorToThrowable, t as AppError, u as flowApiError } from "./errors-BsWrc7px.js";
2
+ export { AppError, AuthError, AuthErrorReason, ConfigError, FlowApiError, FlowApiErrorKind, appErrorToThrowable, authError, configError, flowApiError, leftAuth, leftConfig, leftFlowApi };
package/dist/errors.js ADDED
@@ -0,0 +1,2 @@
1
+ import{Left as e}from"functype";const t=e=>({_tag:`ConfigError`,message:e}),n=(e,t=`config`)=>({_tag:`AuthError`,message:e,reason:t}),r=(e,t,n)=>({_tag:`FlowApiError`,kind:e,message:t,status:n?.status,detail:n?.detail}),i=n=>e(t(n)),a=(t,r)=>e(n(t,r)),o=(t,n,i)=>e(r(t,n,i)),s={auth_expired:`Token cache is stale or the session expired. Restart the server to re-run the device-code sign-in.`,not_found:`The flow, run, or environment was not found in this environment. List the parent resource first to confirm the identifier.`,forbidden:`The signed-in user lacks permission on this resource. Check flow ownership or your Power Automate role.`,rate_limited:`Too many requests to the Power Automate API. Wait a few seconds and retry.`,bad_request:`The request was rejected as malformed. Re-check the parameters against the tool's schema.`,server_error:`The Power Automate service returned a server error. Retry later.`,network:`Could not reach api.flow.microsoft.com. Check network connectivity and try again.`,unsupported_api_drift:`This server uses Microsoft's unofficial api.flow.microsoft.com endpoints, which may have changed shape. Use the report_feedback tool to flag the drift.`},c={auth_expired:`auth error`,not_found:`not found`,forbidden:`forbidden`,rate_limited:`rate limited`,bad_request:`invalid request`,server_error:`server error`,network:`network error`,unsupported_api_drift:`unsupported api drift`},l=e=>{switch(e._tag){case`FlowApiError`:{let t=c[e.kind],n=s[e.kind],r=e.status?` (HTTP ${e.status})`:``;return Error(`${t}${r}: ${e.message}\n\nSuggestion: ${n}`)}case`AuthError`:return Error(`auth error: ${e.message}\n\nSuggestion: ${s.auth_expired}`);case`ConfigError`:return Error(`configuration error: ${e.message}`)}};export{l as appErrorToThrowable,n as authError,t as configError,r as flowApiError,a as leftAuth,i as leftConfig,o as leftFlowApi};
2
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Typed error model for the Power Automate MCP server.\n *\n * All backend calls return `Either<AppError, T>`. Tool `execute` functions convert a\n * `Left` into a thrown `Error` via {@link appErrorToThrowable}; SomaMCP's `wrapTool`\n * then classifies that error (by message keyword) and returns an enriched, agent-facing\n * payload. Because SomaMCP surfaces the error *message* verbatim, the human-readable\n * suggestion must live in the message itself — see {@link FLOW_API_SUGGESTIONS}.\n */\n\nimport { type Either, Left } from \"functype\"\n\n// ── Error variants ────────────────────────────────────────────────────\n\nexport type ConfigError = { readonly _tag: \"ConfigError\"; readonly message: string }\n\nexport type AuthErrorReason =\n | \"config\"\n | \"no_token\"\n | \"expired\"\n | \"device_code_failed\"\n | \"silent_failed\"\n | \"client_credentials_failed\"\n\nexport type AuthError = {\n readonly _tag: \"AuthError\"\n readonly message: string\n readonly reason: AuthErrorReason\n}\n\nexport type FlowApiErrorKind =\n | \"auth_expired\"\n | \"not_found\"\n | \"forbidden\"\n | \"rate_limited\"\n | \"bad_request\"\n | \"server_error\"\n | \"network\"\n | \"unsupported_api_drift\"\n\nexport type FlowApiError = {\n readonly _tag: \"FlowApiError\"\n readonly kind: FlowApiErrorKind\n readonly message: string\n readonly status?: number\n readonly detail?: unknown\n}\n\nexport type AppError = ConfigError | AuthError | FlowApiError\n\n// ── Constructors ──────────────────────────────────────────────────────\n\nexport const configError = (message: string): ConfigError => ({ _tag: \"ConfigError\", message })\n\nexport const authError = (message: string, reason: AuthErrorReason = \"config\"): AuthError => ({\n _tag: \"AuthError\",\n message,\n reason,\n})\n\nexport const flowApiError = (\n kind: FlowApiErrorKind,\n message: string,\n opts?: { status?: number; detail?: unknown },\n): FlowApiError => ({\n _tag: \"FlowApiError\",\n kind,\n message,\n status: opts?.status,\n detail: opts?.detail,\n})\n\n// ── `Left` helpers (save the ceremony at call sites) ──────────────────\n\nexport const leftConfig = <T>(message: string): Either<ConfigError, T> => Left(configError(message))\nexport const leftAuth = <T>(message: string, reason?: AuthErrorReason): Either<AuthError, T> =>\n Left(authError(message, reason))\nexport const leftFlowApi = <T>(\n kind: FlowApiErrorKind,\n message: string,\n opts?: { status?: number; detail?: unknown },\n): Either<FlowApiError, T> => Left(flowApiError(kind, message, opts))\n\n// ── Agent-facing rendering ────────────────────────────────────────────\n\n/**\n * Human-readable next-step for each Flow API failure kind. Embedded into the thrown\n * message so it reaches the agent (SomaMCP's generic per-category suggestions are coarse).\n */\nconst FLOW_API_SUGGESTIONS: Record<FlowApiErrorKind, string> = {\n auth_expired: \"Token cache is stale or the session expired. Restart the server to re-run the device-code sign-in.\",\n not_found:\n \"The flow, run, or environment was not found in this environment. List the parent resource first to confirm the identifier.\",\n forbidden: \"The signed-in user lacks permission on this resource. Check flow ownership or your Power Automate role.\",\n rate_limited: \"Too many requests to the Power Automate API. Wait a few seconds and retry.\",\n bad_request: \"The request was rejected as malformed. Re-check the parameters against the tool's schema.\",\n server_error: \"The Power Automate service returned a server error. Retry later.\",\n network: \"Could not reach api.flow.microsoft.com. Check network connectivity and try again.\",\n unsupported_api_drift:\n \"This server uses Microsoft's unofficial api.flow.microsoft.com endpoints, which may have changed shape. Use the report_feedback tool to flag the drift.\",\n}\n\n/**\n * Leading phrase chosen so SomaMCP's `classifyError` (substring match) buckets the error\n * correctly: \"auth\"→auth, \"not found\"→not_found, \"forbidden\"→auth, \"invalid\"→validation.\n */\nconst FLOW_API_CLASSIFY_PREFIX: Record<FlowApiErrorKind, string> = {\n auth_expired: \"auth error\",\n not_found: \"not found\",\n forbidden: \"forbidden\",\n rate_limited: \"rate limited\",\n bad_request: \"invalid request\",\n server_error: \"server error\",\n network: \"network error\",\n unsupported_api_drift: \"unsupported api drift\",\n}\n\n/**\n * Convert an {@link AppError} into a throwable `Error` whose message both classifies well\n * under SomaMCP and carries an actionable suggestion for the agent.\n */\nexport const appErrorToThrowable = (error: AppError): Error => {\n switch (error._tag) {\n case \"FlowApiError\": {\n const prefix = FLOW_API_CLASSIFY_PREFIX[error.kind]\n const suggestion = FLOW_API_SUGGESTIONS[error.kind]\n const status = error.status ? ` (HTTP ${error.status})` : \"\"\n return new Error(`${prefix}${status}: ${error.message}\\n\\nSuggestion: ${suggestion}`)\n }\n case \"AuthError\":\n return new Error(`auth error: ${error.message}\\n\\nSuggestion: ${FLOW_API_SUGGESTIONS.auth_expired}`)\n case \"ConfigError\":\n return new Error(`configuration error: ${error.message}`)\n }\n}\n"],"mappings":"gCAoDA,MAAa,EAAe,IAAkC,CAAE,KAAM,cAAe,SAAQ,GAEhF,GAAa,EAAiB,EAA0B,YAAyB,CAC5F,KAAM,YACN,UACA,QACF,GAEa,GACX,EACA,EACA,KACkB,CAClB,KAAM,eACN,OACA,UACA,OAAQ,GAAM,OACd,OAAQ,GAAM,MAChB,GAIa,EAAiB,GAA4C,EAAK,EAAY,CAAO,CAAC,EACtF,GAAe,EAAiB,IAC3C,EAAK,EAAU,EAAS,CAAM,CAAC,EACpB,GACX,EACA,EACA,IAC4B,EAAK,EAAa,EAAM,EAAS,CAAI,CAAC,EAQ9D,EAAyD,CAC7D,aAAc,qGACd,UACE,6HACF,UAAW,0GACX,aAAc,6EACd,YAAa,4FACb,aAAc,mEACd,QAAS,oFACT,sBACE,yJACJ,EAMM,EAA6D,CACjE,aAAc,aACd,UAAW,YACX,UAAW,YACX,aAAc,eACd,YAAa,kBACb,aAAc,eACd,QAAS,gBACT,sBAAuB,uBACzB,EAMa,EAAuB,GAA2B,CAC7D,OAAQ,EAAM,KAAd,CACE,IAAK,eAAgB,CACnB,IAAM,EAAS,EAAyB,EAAM,MACxC,EAAa,EAAqB,EAAM,MACxC,EAAS,EAAM,OAAS,UAAU,EAAM,OAAO,GAAK,GAC1D,OAAW,MAAM,GAAG,IAAS,EAAO,IAAI,EAAM,QAAQ,kBAAkB,GAAY,CACtF,CACA,IAAK,YACH,OAAW,MAAM,eAAe,EAAM,QAAQ,kBAAkB,EAAqB,cAAc,EACrG,IAAK,cACH,OAAW,MAAM,wBAAwB,EAAM,SAAS,CAC5D,CACF"}
@@ -0,0 +1,16 @@
1
+ import { a as FlowApiError } from "./errors-BsWrc7px.js";
2
+ import { t as FlowApiClient } from "./client-DCWt6ssY.js";
3
+ import { a as FlowSummary, r as FlowDetail } from "./types-DRNRhdJl.js";
4
+ import { Either } from "functype";
5
+
6
+ //#region src/backend/flow-api/flows.d.ts
7
+ type FlowFilter = {
8
+ owner?: string;
9
+ };
10
+ declare const listFlows: (client: FlowApiClient, env: string, filter?: FlowFilter) => Promise<Either<FlowApiError, FlowSummary[]>>;
11
+ declare const getFlow: (client: FlowApiClient, env: string, flow: string) => Promise<Either<FlowApiError, FlowDetail>>;
12
+ declare const enableFlow: (client: FlowApiClient, env: string, flow: string) => Promise<Either<FlowApiError, unknown>>;
13
+ declare const disableFlow: (client: FlowApiClient, env: string, flow: string) => Promise<Either<FlowApiError, unknown>>;
14
+ //#endregion
15
+ export { listFlows as a, getFlow as i, disableFlow as n, enableFlow as r, FlowFilter as t };
16
+ //# sourceMappingURL=flows-B0jBc1wf.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { i as ServerConfig } from "./types-HGbUI9Dc.js";
2
+ import { n as Environment } from "./types-DRNRhdJl.js";
3
+ import { FlowBackend } from "./backend/index.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { PKG_VERSION } from "./version.js";
6
+ import { SomaServerInstance } from "somamcp";
7
+
8
+ //#region src/index.d.ts
9
+ declare const createPowerAutomateServer: (config: ServerConfig) => SomaServerInstance;
10
+ //#endregion
11
+ export { type Environment, type FlowBackend, PKG_VERSION, type ServerConfig, createPowerAutomateServer, loadConfig };
12
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{loadConfig as e}from"./config.js";import{createTokenManager as t}from"./auth/token-manager.js";import{createFlowApiBackend as n}from"./backend/index.js";import{registerConnectionTools as r}from"./tools/connections.js";import{registerEnvironmentTools as i}from"./tools/environments.js";import{registerFlowReadTools as a,registerFlowWriteTools as o}from"./tools/flows.js";import{registerOwnerReadTools as s,registerOwnerWriteTools as c}from"./tools/owners.js";import{registerRunReadTools as l,registerRunWriteTools as u}from"./tools/runs.js";import{PKG_VERSION as d}from"./version.js";import{createCompositeTelemetry as f,createConsoleTelemetry as p,createFeedbackTool as m,createGithubFeedback as h,createJsonFileTelemetry as g,createServer as _}from"somamcp";const v=e=>{let t=[];if(e.telemetry.includes(`console`)&&t.push(p(`[power-automate]`)),e.telemetry.includes(`file`)&&t.push(g({filePath:e.telemetryFilePath})),t.length!==0)return t.length===1?t[0]:f(t)},y=e=>{if(e&&typeof e==`object`&&`headers`in e){let t=e.headers;if(t&&typeof t==`object`){let e=t,n=e.authorization??e.Authorization;if(typeof n==`string`&&n.toLowerCase().startsWith(`bearer `))return n.slice(7).trim()}}},b=e=>{let t=e.mcpApiKey;if(t)return async e=>{if(y(e)!==t)throw Error(`unauthorized: invalid or missing bearer token`);return{authorized:!0}}},x=e=>{let f=n({tokenProvider:t(e)}),p=_({name:`power-automate-mcp`,version:d,instructions:[`Manage Microsoft Power Automate cloud flows: list/inspect flows, debug runs, check connections and owners.`,`Read-only by default; set ENABLE_WRITE_OPS=true to enable mutating tools.`,`Uses Microsoft's unofficial api.flow.microsoft.com management API (labeled unsupported by Microsoft).`].join(` `),telemetry:v(e),authenticate:b(e)}),g={defaultEnvironment:e.defaultEnvironment},y={defaultEnvironment:e.defaultEnvironment,enableWrite:e.enableWriteOps};return i(p,f),a(p,f,g),l(p,f,g),r(p,f,g),s(p,f,g),o(p,f,y),u(p,f,y),c(p,f,y),p.addTool(m({name:`report_feedback`,extraLabels:[`mcp-feedback`],provider:h({repo:e.feedbackRepo,getToken:()=>process.env.GITHUB_TOKEN||void 0})})),p};export{d as PKG_VERSION,x as createPowerAutomateServer,e as loadConfig};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Power Automate MCP server — library entry point.\n *\n * Wires SomaMCP (telemetry, info/health/dashboard, error classification) to a Flow API\n * backend and registers the tool catalog. Read-only tools are always registered; mutating\n * tools are gated behind `ENABLE_WRITE_OPS=true`.\n *\n * See ./bin.ts for the CLI/transport entry point.\n */\n\nimport {\n createCompositeTelemetry,\n createConsoleTelemetry,\n createFeedbackTool,\n createGithubFeedback,\n createJsonFileTelemetry,\n createServer,\n type SomaServerInstance,\n type TelemetryCollector,\n} from \"somamcp\"\n\nimport { createTokenManager } from \"./auth/token-manager.js\"\nimport type { ServerConfig } from \"./auth/types.js\"\nimport { createFlowApiBackend } from \"./backend/index.js\"\nimport { registerConnectionTools } from \"./tools/connections.js\"\nimport { registerEnvironmentTools } from \"./tools/environments.js\"\nimport { registerFlowReadTools, registerFlowWriteTools } from \"./tools/flows.js\"\nimport { registerOwnerReadTools, registerOwnerWriteTools } from \"./tools/owners.js\"\nimport { registerRunReadTools, registerRunWriteTools } from \"./tools/runs.js\"\nimport { PKG_VERSION } from \"./version.js\"\n\nconst buildTelemetry = (config: ServerConfig): TelemetryCollector | undefined => {\n const collectors: TelemetryCollector[] = []\n if (config.telemetry.includes(\"console\")) collectors.push(createConsoleTelemetry(\"[power-automate]\"))\n if (config.telemetry.includes(\"file\"))\n collectors.push(createJsonFileTelemetry({ filePath: config.telemetryFilePath }))\n if (collectors.length === 0) return undefined\n if (collectors.length === 1) return collectors[0]\n return createCompositeTelemetry(collectors)\n}\n\nconst extractBearer = (request: unknown): string | undefined => {\n if (request && typeof request === \"object\" && \"headers\" in request) {\n const headers = (request as { headers?: unknown }).headers\n if (headers && typeof headers === \"object\") {\n const record = headers as Record<string, unknown>\n const auth = record.authorization ?? record.Authorization\n if (typeof auth === \"string\" && auth.toLowerCase().startsWith(\"bearer \")) return auth.slice(7).trim()\n }\n }\n return undefined\n}\n\n/**\n * Protects the HTTP operational endpoints (and HTTP MCP calls) with a static bearer token.\n * Not invoked on stdio. Returns undefined when MCP_API_KEY is unset (local/dev: open).\n */\nconst buildAuthenticate = (\n config: ServerConfig,\n): ((request: unknown) => Promise<Record<string, unknown> | undefined>) | undefined => {\n const apiKey = config.mcpApiKey\n if (!apiKey) return undefined\n return async (request: unknown): Promise<Record<string, unknown> | undefined> => {\n if (extractBearer(request) !== apiKey) {\n throw new Error(\"unauthorized: invalid or missing bearer token\")\n }\n return { authorized: true }\n }\n}\n\nexport const createPowerAutomateServer = (config: ServerConfig): SomaServerInstance => {\n const tokenManager = createTokenManager(config)\n const backend = createFlowApiBackend({ tokenProvider: tokenManager })\n\n const server = createServer({\n name: \"power-automate-mcp\",\n version: PKG_VERSION,\n instructions: [\n \"Manage Microsoft Power Automate cloud flows: list/inspect flows, debug runs, check connections and owners.\",\n \"Read-only by default; set ENABLE_WRITE_OPS=true to enable mutating tools.\",\n \"Uses Microsoft's unofficial api.flow.microsoft.com management API (labeled unsupported by Microsoft).\",\n ].join(\" \"),\n telemetry: buildTelemetry(config),\n authenticate: buildAuthenticate(config),\n })\n\n const readOpts = { defaultEnvironment: config.defaultEnvironment }\n const writeOpts = { defaultEnvironment: config.defaultEnvironment, enableWrite: config.enableWriteOps }\n\n registerEnvironmentTools(server, backend)\n registerFlowReadTools(server, backend, readOpts)\n registerRunReadTools(server, backend, readOpts)\n registerConnectionTools(server, backend, readOpts)\n registerOwnerReadTools(server, backend, readOpts)\n\n registerFlowWriteTools(server, backend, writeOpts)\n registerRunWriteTools(server, backend, writeOpts)\n registerOwnerWriteTools(server, backend, writeOpts)\n\n // report_feedback — lets agents self-report API drift / bugs as GitHub issues.\n // Submits only when GITHUB_TOKEN is set; otherwise the tool reports that gracefully.\n server.addTool(\n createFeedbackTool({\n name: \"report_feedback\",\n extraLabels: [\"mcp-feedback\"],\n provider: createGithubFeedback({\n repo: config.feedbackRepo as `${string}/${string}`,\n getToken: () => process.env.GITHUB_TOKEN || undefined,\n }),\n }),\n )\n\n return server\n}\n\nexport type { ServerConfig } from \"./auth/types.js\"\nexport type { FlowBackend } from \"./backend/index.js\"\nexport type { Environment } from \"./backend/types.js\"\nexport { loadConfig } from \"./config.js\"\nexport { PKG_VERSION } from \"./version.js\"\n"],"mappings":"8vBA+BA,MAAM,EAAkB,GAAyD,CAC/E,IAAM,EAAmC,CAAC,EAC1C,GAAI,EAAO,UAAU,SAAS,SAAS,GAAG,EAAW,KAAK,EAAuB,kBAAkB,CAAC,EAChG,EAAO,UAAU,SAAS,MAAM,GAClC,EAAW,KAAK,EAAwB,CAAE,SAAU,EAAO,iBAAkB,CAAC,CAAC,EAC7E,EAAW,SAAW,EAE1B,OADI,EAAW,SAAW,EAAU,EAAW,GACxC,EAAyB,CAAU,CAC5C,EAEM,EAAiB,GAAyC,CAC9D,GAAI,GAAW,OAAO,GAAY,UAAY,YAAa,EAAS,CAClE,IAAM,EAAW,EAAkC,QACnD,GAAI,GAAW,OAAO,GAAY,SAAU,CAC1C,IAAM,EAAS,EACT,EAAO,EAAO,eAAiB,EAAO,cAC5C,GAAI,OAAO,GAAS,UAAY,EAAK,YAAY,EAAE,WAAW,SAAS,EAAG,OAAO,EAAK,MAAM,CAAC,EAAE,KAAK,CACtG,CACF,CAEF,EAMM,EACJ,GACqF,CACrF,IAAM,EAAS,EAAO,UACjB,KACL,OAAO,KAAO,IAAmE,CAC/E,GAAI,EAAc,CAAO,IAAM,EAC7B,MAAU,MAAM,+CAA+C,EAEjE,MAAO,CAAE,WAAY,EAAK,CAC5B,CACF,EAEa,EAA6B,GAA6C,CAErF,IAAM,EAAU,EAAqB,CAAE,cADlB,EAAmB,CACyB,CAAE,CAAC,EAE9D,EAAS,EAAa,CAC1B,KAAM,qBACN,QAAS,EACT,aAAc,CACZ,6GACA,4EACA,uGACF,EAAE,KAAK,GAAG,EACV,UAAW,EAAe,CAAM,EAChC,aAAc,EAAkB,CAAM,CACxC,CAAC,EAEK,EAAW,CAAE,mBAAoB,EAAO,kBAAmB,EAC3D,EAAY,CAAE,mBAAoB,EAAO,mBAAoB,YAAa,EAAO,cAAe,EAyBtG,OAvBA,EAAyB,EAAQ,CAAO,EACxC,EAAsB,EAAQ,EAAS,CAAQ,EAC/C,EAAqB,EAAQ,EAAS,CAAQ,EAC9C,EAAwB,EAAQ,EAAS,CAAQ,EACjD,EAAuB,EAAQ,EAAS,CAAQ,EAEhD,EAAuB,EAAQ,EAAS,CAAS,EACjD,EAAsB,EAAQ,EAAS,CAAS,EAChD,EAAwB,EAAQ,EAAS,CAAS,EAIlD,EAAO,QACL,EAAmB,CACjB,KAAM,kBACN,YAAa,CAAC,cAAc,EAC5B,SAAU,EAAqB,CAC7B,KAAM,EAAO,aACb,aAAgB,QAAQ,IAAI,cAAgB,IAAA,EAC9C,CAAC,CACH,CAAC,CACH,EAEO,CACT"}
@@ -0,0 +1,18 @@
1
+ import { a as FlowApiError } from "./errors-BsWrc7px.js";
2
+ import { t as FlowApiClient } from "./client-DCWt6ssY.js";
3
+ import { i as FlowOwner } from "./types-DRNRhdJl.js";
4
+ import { Either } from "functype";
5
+
6
+ //#region src/backend/flow-api/owners.d.ts
7
+ declare const listFlowOwners: (client: FlowApiClient, env: string, flow: string) => Promise<Either<FlowApiError, FlowOwner[]>>;
8
+ type OwnerRole = "CanEdit" | "CanView";
9
+ /**
10
+ * Grant a principal a role on a flow. The exact PUT permissions body shape for this
11
+ * unofficial endpoint is not firmly documented — verify against the portal's network tab
12
+ * and update docs/api-notes.md if the API rejects this payload.
13
+ */
14
+ declare const addFlowOwner: (client: FlowApiClient, env: string, flow: string, principalId: string, roleName: OwnerRole) => Promise<Either<FlowApiError, unknown>>;
15
+ declare const removeFlowOwner: (client: FlowApiClient, env: string, flow: string, principalId: string) => Promise<Either<FlowApiError, unknown>>;
16
+ //#endregion
17
+ export { removeFlowOwner as i, addFlowOwner as n, listFlowOwners as r, OwnerRole as t };
18
+ //# sourceMappingURL=owners-CdI2c9rS.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { a as FlowApiError } from "./errors-BsWrc7px.js";
2
+ import { t as FlowApiClient } from "./client-DCWt6ssY.js";
3
+ import { c as RunSummary, o as RunDetail } from "./types-DRNRhdJl.js";
4
+ import { Either } from "functype";
5
+
6
+ //#region src/backend/flow-api/runs.d.ts
7
+ type RunListOpts = {
8
+ top?: number;
9
+ status?: string;
10
+ };
11
+ declare const listRuns: (client: FlowApiClient, env: string, flow: string, opts?: RunListOpts) => Promise<Either<FlowApiError, RunSummary[]>>;
12
+ declare const getRun: (client: FlowApiClient, env: string, flow: string, run: string) => Promise<Either<FlowApiError, RunDetail>>;
13
+ declare const cancelRun: (client: FlowApiClient, env: string, flow: string, run: string) => Promise<Either<FlowApiError, unknown>>;
14
+ declare const resubmitRun: (client: FlowApiClient, env: string, flow: string, trigger: string, run: string) => Promise<Either<FlowApiError, unknown>>;
15
+ //#endregion
16
+ export { resubmitRun as a, listRuns as i, cancelRun as n, getRun as r, RunListOpts as t };
17
+ //# sourceMappingURL=runs-DdpaFLpn.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { n as AuthError } from "./errors-BsWrc7px.js";
2
+ import { i as ServerConfig } from "./types-HGbUI9Dc.js";
3
+ import { Either } from "functype";
4
+
5
+ //#region src/auth/token-manager.d.ts
6
+ type TokenProvider = {
7
+ getToken: () => Promise<Either<AuthError, string>>;
8
+ };
9
+ type TokenManagerOptions = {
10
+ /** Sink for device-code prompts and the winning-scope diagnostic. Defaults to stderr. */log?: (message: string) => void;
11
+ };
12
+ declare const createTokenManager: (config: ServerConfig, opts?: TokenManagerOptions) => TokenProvider;
13
+ //#endregion
14
+ export { TokenProvider as n, createTokenManager as r, TokenManagerOptions as t };
15
+ //# sourceMappingURL=token-manager-BfUM20Tf.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { FlowBackend } from "../backend/index.js";
2
+ import { ReadToolOptions } from "./shared.js";
3
+ import { SomaServerInstance } from "somamcp";
4
+
5
+ //#region src/tools/connections.d.ts
6
+ declare const registerConnectionTools: (server: SomaServerInstance, backend: FlowBackend, opts?: ReadToolOptions) => void;
7
+ //#endregion
8
+ export { registerConnectionTools };
9
+ //# sourceMappingURL=connections.d.ts.map
@@ -0,0 +1,3 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{DISCLAIMER as t,renderResult as n,resolveEnvironment as r}from"./shared.js";import{z as i}from"zod";const a=(a,o,s={})=>{a.addTool({name:`list_connections`,description:[`List the connections (API connectors) in an environment and their health.`,`Returns: array of { name, apiName, displayName, status, accountName, expiresAt }. A status other than "Connected" indicates a broken/expired connection — a common cause of silently failing flows.`,`Parameters: environment (optional).`,t,`Example: list_connections {}`].join(`
2
+ `),annotations:{readOnlyHint:!0,title:`List connections`},parameters:i.object({environment:i.string().optional().describe(`Environment id (from list_environments). Omit to use the user's default environment.`)}),execute:async({environment:t})=>(await r(o,s.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>n(await o.listConnections(e)))})};export{a as registerConnectionTools};
3
+ //# sourceMappingURL=connections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connections.js","names":[],"sources":["../../src/tools/connections.ts"],"sourcesContent":["/**\n * Read-only connection tool: list_connections.\n */\n\nimport type { SomaServerInstance } from \"somamcp\"\nimport { z } from \"zod\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { appErrorToThrowable } from \"../errors.js\"\nimport { DISCLAIMER, type ReadToolOptions, renderResult, resolveEnvironment } from \"./shared.js\"\n\nexport const registerConnectionTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: ReadToolOptions = {},\n): void => {\n server.addTool({\n name: \"list_connections\",\n description: [\n \"List the connections (API connectors) in an environment and their health.\",\n 'Returns: array of { name, apiName, displayName, status, accountName, expiresAt }. A status other than \"Connected\" indicates a broken/expired connection — a common cause of silently failing flows.',\n \"Parameters: environment (optional).\",\n DISCLAIMER,\n \"Example: list_connections {}\",\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"List connections\" },\n parameters: z.object({\n environment: z\n .string()\n .optional()\n .describe(\"Environment id (from list_environments). Omit to use the user's default environment.\"),\n }),\n execute: async ({ environment }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.listConnections(env)),\n ),\n })\n}\n"],"mappings":"8JAWA,MAAa,GACX,EACA,EACA,EAAwB,CAAC,IAChB,CACT,EAAO,QAAQ,CACb,KAAM,mBACN,YAAa,CACX,4EACA,sMACA,sCACA,EACA,8BACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,kBAAmB,EAC7D,WAAY,EAAE,OAAO,CACnB,YAAa,EACV,OAAO,EACP,SAAS,EACT,SAAS,sFAAsF,CACpG,CAAC,EACD,QAAS,MAAO,CAAE,kBACf,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,gBAAgB,CAAG,CAAC,CAChE,CACJ,CAAC,CACH"}
@@ -0,0 +1,8 @@
1
+ import { FlowBackend } from "../backend/index.js";
2
+ import { SomaServerInstance } from "somamcp";
3
+
4
+ //#region src/tools/environments.d.ts
5
+ declare const registerEnvironmentTools: (server: SomaServerInstance, backend: FlowBackend) => void;
6
+ //#endregion
7
+ export { registerEnvironmentTools };
8
+ //# sourceMappingURL=environments.d.ts.map
@@ -0,0 +1,3 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{z as t}from"zod";const n=(n,r)=>{n.addTool({name:`list_environments`,description:[`List the Power Automate environments visible to the signed-in user.`,"Returns: { id, name, displayName, location, isDefault }. The entry with isDefault=true is the user's default environment, used by other tools when `environment` is omitted.",`Parameters: none.`,`Uses Microsoft's unofficial api.flow.microsoft.com endpoint, which Microsoft labels unsupported; behavior may change.`,`Example: list_environments {}`].join(`
2
+ `),annotations:{readOnlyHint:!0,title:`List Power Automate environments`},parameters:t.object({}),execute:async()=>(await r.listEnvironments()).fold(t=>{throw e(t)},e=>JSON.stringify(e,null,2))})};export{n as registerEnvironmentTools};
3
+ //# sourceMappingURL=environments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environments.js","names":[],"sources":["../../src/tools/environments.ts"],"sourcesContent":["/**\n * `list_environments` — the entry point for every other tool (everything else needs an\n * environment id). Read-only.\n */\n\nimport type { SomaServerInstance } from \"somamcp\"\nimport { z } from \"zod\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { appErrorToThrowable } from \"../errors.js\"\n\nconst DISCLAIMER =\n \"Uses Microsoft's unofficial api.flow.microsoft.com endpoint, which Microsoft labels unsupported; behavior may change.\"\n\nexport const registerEnvironmentTools = (server: SomaServerInstance, backend: FlowBackend): void => {\n server.addTool({\n name: \"list_environments\",\n description: [\n \"List the Power Automate environments visible to the signed-in user.\",\n \"Returns: { id, name, displayName, location, isDefault }. The entry with isDefault=true is the user's default environment, used by other tools when `environment` is omitted.\",\n \"Parameters: none.\",\n DISCLAIMER,\n \"Example: list_environments {}\",\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"List Power Automate environments\" },\n parameters: z.object({}),\n execute: async () => {\n const result = await backend.listEnvironments()\n return result.fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n (environments) => JSON.stringify(environments, null, 2),\n )\n },\n })\n}\n"],"mappings":"2EAWA,MAGa,GAA4B,EAA4B,IAA+B,CAClG,EAAO,QAAQ,CACb,KAAM,oBACN,YAAa,CACX,sEACA,+KACA,oBACA,wHACA,+BACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,kCAAmC,EAC7E,WAAY,EAAE,OAAO,CAAC,CAAC,EACvB,QAAS,UAEA,MADc,EAAQ,iBAAiB,GAChC,KACX,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACC,GAAiB,KAAK,UAAU,EAAc,KAAM,CAAC,CACxD,CAEJ,CAAC,CACH"}
@@ -0,0 +1,10 @@
1
+ import { FlowBackend } from "../backend/index.js";
2
+ import { ReadToolOptions, WriteToolOptions } from "./shared.js";
3
+ import { SomaServerInstance } from "somamcp";
4
+
5
+ //#region src/tools/flows.d.ts
6
+ declare const registerFlowReadTools: (server: SomaServerInstance, backend: FlowBackend, opts?: ReadToolOptions) => void;
7
+ declare const registerFlowWriteTools: (server: SomaServerInstance, backend: FlowBackend, opts: WriteToolOptions) => void;
8
+ //#endregion
9
+ export { registerFlowReadTools, registerFlowWriteTools };
10
+ //# sourceMappingURL=flows.d.ts.map
@@ -0,0 +1,6 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{DISCLAIMER as t,confirmWrite as n,ensureWriteEnabled as r,renderResult as i,resolveEnvironment as a}from"./shared.js";import{z as o}from"zod";const s=o.string().optional().describe(`Environment id (from list_environments). Omit to use the user's default environment.`),c=(n,r,c={})=>{n.addTool({name:`list_flows`,description:[`List cloud flows in an environment.`,"Returns: array of { name, displayName, state, createdTime, lastModifiedTime, owner }. `name` is the flow GUID used by the other flow tools; `state` is Started/Stopped/Suspended.",`Parameters: environment (optional), owner (optional — filter by creator userId/email).`,t,`Example: list_flows { "owner": "user@contoso.com" }`].join(`
2
+ `),annotations:{readOnlyHint:!0,title:`List Power Automate flows`},parameters:o.object({environment:s,owner:o.string().optional().describe(`Filter to flows created by this principal (userId or email).`)}),execute:async({environment:t,owner:n})=>(await a(r,c.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>i(await r.listFlows(e,n?{owner:n}:void 0)))}),n.addTool({name:`get_flow`,description:["Get the full definition of one flow: state, timestamps, owner, the workflow `definition` JSON, connectionReferences, and parsed trigger/action names.",`Parameters: flow (required, GUID from list_flows), environment (optional).`,t,`Example: get_flow { "flow": "00000000-0000-0000-0000-000000000000" }`].join(`
3
+ `),annotations:{readOnlyHint:!0,title:`Get flow definition`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`)}),execute:async({environment:t,flow:n})=>(await a(r,c.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>i(await r.getFlow(e,n)))})},l=(i,c,l)=>{let u=l.enableWrite?``:` [DISABLED: set ENABLE_WRITE_OPS=true]`;i.addTool({name:`enable_flow`,description:[`Turn a flow on (POST .../start)${u}.`,`Parameters: flow (required), environment (optional). Verify with get_flow (state -> Started).`,t].join(`
4
+ `),annotations:{destructiveHint:!0,title:`Enable flow`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`)}),execute:async({environment:t,flow:i})=>(r(l.enableWrite,`enable_flow`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.enableFlow(t,i)).fold(t=>{throw e(t)},()=>n(`enable_flow`,{environment:t,flow:i}))))}),i.addTool({name:`disable_flow`,description:[`Turn a flow off (POST .../stop)${u}.`,`Parameters: flow (required), environment (optional). Verify with get_flow (state -> Stopped).`,t].join(`
5
+ `),annotations:{destructiveHint:!0,title:`Disable flow`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`)}),execute:async({environment:t,flow:i})=>(r(l.enableWrite,`disable_flow`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.disableFlow(t,i)).fold(t=>{throw e(t)},()=>n(`disable_flow`,{environment:t,flow:i}))))})};export{c as registerFlowReadTools,l as registerFlowWriteTools};
6
+ //# sourceMappingURL=flows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flows.js","names":[],"sources":["../../src/tools/flows.ts"],"sourcesContent":["/**\n * Read-only flow tools: list_flows, get_flow.\n */\n\nimport type { SomaServerInstance } from \"somamcp\"\nimport { z } from \"zod\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { appErrorToThrowable } from \"../errors.js\"\nimport {\n confirmWrite,\n DISCLAIMER,\n ensureWriteEnabled,\n type ReadToolOptions,\n renderResult,\n resolveEnvironment,\n type WriteToolOptions,\n} from \"./shared.js\"\n\nconst ENV_PARAM = z\n .string()\n .optional()\n .describe(\"Environment id (from list_environments). Omit to use the user's default environment.\")\n\nexport const registerFlowReadTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: ReadToolOptions = {},\n): void => {\n server.addTool({\n name: \"list_flows\",\n description: [\n \"List cloud flows in an environment.\",\n \"Returns: array of { name, displayName, state, createdTime, lastModifiedTime, owner }. `name` is the flow GUID used by the other flow tools; `state` is Started/Stopped/Suspended.\",\n \"Parameters: environment (optional), owner (optional — filter by creator userId/email).\",\n DISCLAIMER,\n 'Example: list_flows { \"owner\": \"user@contoso.com\" }',\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"List Power Automate flows\" },\n parameters: z.object({\n environment: ENV_PARAM,\n owner: z.string().optional().describe(\"Filter to flows created by this principal (userId or email).\"),\n }),\n execute: async ({ environment, owner }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.listFlows(env, owner ? { owner } : undefined)),\n ),\n })\n\n server.addTool({\n name: \"get_flow\",\n description: [\n \"Get the full definition of one flow: state, timestamps, owner, the workflow `definition` JSON, connectionReferences, and parsed trigger/action names.\",\n \"Parameters: flow (required, GUID from list_flows), environment (optional).\",\n DISCLAIMER,\n 'Example: get_flow { \"flow\": \"00000000-0000-0000-0000-000000000000\" }',\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"Get flow definition\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n }),\n execute: async ({ environment, flow }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.getFlow(env, flow)),\n ),\n })\n}\n\nexport const registerFlowWriteTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: WriteToolOptions,\n): void => {\n const gated = opts.enableWrite ? \"\" : \" [DISABLED: set ENABLE_WRITE_OPS=true]\"\n\n server.addTool({\n name: \"enable_flow\",\n description: [\n `Turn a flow on (POST .../start)${gated}.`,\n \"Parameters: flow (required), environment (optional). Verify with get_flow (state -> Started).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Enable flow\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n }),\n execute: async ({ environment, flow }) => {\n ensureWriteEnabled(opts.enableWrite, \"enable_flow\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.enableFlow(env, flow)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"enable_flow\", { environment: env, flow }),\n ),\n )\n },\n })\n\n server.addTool({\n name: \"disable_flow\",\n description: [\n `Turn a flow off (POST .../stop)${gated}.`,\n \"Parameters: flow (required), environment (optional). Verify with get_flow (state -> Stopped).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Disable flow\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n }),\n execute: async ({ environment, flow }) => {\n ensureWriteEnabled(opts.enableWrite, \"disable_flow\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.disableFlow(env, flow)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"disable_flow\", { environment: env, flow }),\n ),\n )\n },\n })\n}\n"],"mappings":"wMAmBA,MAAM,EAAY,EACf,OAAO,EACP,SAAS,EACT,SAAS,sFAAsF,EAErF,GACX,EACA,EACA,EAAwB,CAAC,IAChB,CACT,EAAO,QAAQ,CACb,KAAM,aACN,YAAa,CACX,sCACA,oLACA,yFACA,EACA,qDACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,2BAA4B,EACtE,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,MAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8DAA8D,CACtG,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,YAC5B,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,UAAU,EAAK,EAAQ,CAAE,OAAM,EAAI,IAAA,EAAS,CAAC,CACzF,CACJ,CAAC,EAED,EAAO,QAAQ,CACb,KAAM,WACN,YAAa,CACX,wJACA,6EACA,EACA,sEACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,qBAAsB,EAChE,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,CAC/D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,WAC5B,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,QAAQ,EAAK,CAAI,CAAC,CAC9D,CACJ,CAAC,CACH,EAEa,GACX,EACA,EACA,IACS,CACT,IAAM,EAAQ,EAAK,YAAc,GAAK,yCAEtC,EAAO,QAAQ,CACb,KAAM,cACN,YAAa,CACX,kCAAkC,EAAM,GACxC,gGACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,aAAc,EAC3D,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,CAC/D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,WAC7B,EAAmB,EAAK,YAAa,aAAa,GAC1C,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,WAAW,EAAK,CAAI,GAAG,KACnC,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,cAAe,CAAE,YAAa,EAAK,MAAK,CAAC,CAC9D,CACJ,EAEJ,CAAC,EAED,EAAO,QAAQ,CACb,KAAM,eACN,YAAa,CACX,kCAAkC,EAAM,GACxC,gGACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,cAAe,EAC5D,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,CAC/D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,WAC7B,EAAmB,EAAK,YAAa,cAAc,GAC3C,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,YAAY,EAAK,CAAI,GAAG,KACpC,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,eAAgB,CAAE,YAAa,EAAK,MAAK,CAAC,CAC/D,CACJ,EAEJ,CAAC,CACH"}
@@ -0,0 +1,10 @@
1
+ import { FlowBackend } from "../backend/index.js";
2
+ import { ReadToolOptions, WriteToolOptions } from "./shared.js";
3
+ import { SomaServerInstance } from "somamcp";
4
+
5
+ //#region src/tools/owners.d.ts
6
+ declare const registerOwnerReadTools: (server: SomaServerInstance, backend: FlowBackend, opts?: ReadToolOptions) => void;
7
+ declare const registerOwnerWriteTools: (server: SomaServerInstance, backend: FlowBackend, opts: WriteToolOptions) => void;
8
+ //#endregion
9
+ export { registerOwnerReadTools, registerOwnerWriteTools };
10
+ //# sourceMappingURL=owners.d.ts.map
@@ -0,0 +1,5 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{DISCLAIMER as t,confirmWrite as n,ensureWriteEnabled as r,renderResult as i,resolveEnvironment as a}from"./shared.js";import{z as o}from"zod";const s=o.string().optional().describe(`Environment id (from list_environments). Omit to use the user's default environment.`),c=(n,r,s={})=>{n.addTool({name:`list_flow_owners`,description:[`List the owners/permissions on a flow.`,`Returns: array of { principalId, principalType, roleName, principalDisplayName }.`,`Parameters: flow (required), environment (optional).`,t,`Example: list_flow_owners { "flow": "<guid>" }`].join(`
2
+ `),annotations:{readOnlyHint:!0,title:`List flow owners`},parameters:o.object({environment:o.string().optional().describe(`Environment id (from list_environments). Omit to use the user's default environment.`),flow:o.string().describe(`Flow GUID name (from list_flows).`)}),execute:async({environment:t,flow:n})=>(await a(r,s.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>i(await r.listFlowOwners(e,n)))})},l=(i,c,l)=>{let u=l.enableWrite?``:` [DISABLED: set ENABLE_WRITE_OPS=true]`;i.addTool({name:`add_flow_owner`,description:[`Grant a principal a role on a flow (PUT .../permissions/{principalId})${u}.`,`Parameters: flow (required), principalId (required — AAD object id), roleName (CanEdit|CanView), environment (optional).`,t].join(`
3
+ `),annotations:{destructiveHint:!0,title:`Add flow owner`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),principalId:o.string().describe(`Azure AD object id of the user/group to grant.`),roleName:o.enum([`CanEdit`,`CanView`]).describe(`Role to grant.`)}),execute:async({environment:t,flow:i,principalId:o,roleName:s})=>(r(l.enableWrite,`add_flow_owner`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.addFlowOwner(t,i,o,s)).fold(t=>{throw e(t)},()=>n(`add_flow_owner`,{environment:t,flow:i,principalId:o,roleName:s}))))}),i.addTool({name:`remove_flow_owner`,description:[`Revoke a principal's access to a flow (DELETE .../permissions/{principalId})${u}.`,`Parameters: flow (required), principalId (required), environment (optional).`,t].join(`
4
+ `),annotations:{destructiveHint:!0,title:`Remove flow owner`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),principalId:o.string().describe(`Azure AD object id of the user/group to revoke.`)}),execute:async({environment:t,flow:i,principalId:o})=>(r(l.enableWrite,`remove_flow_owner`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.removeFlowOwner(t,i,o)).fold(t=>{throw e(t)},()=>n(`remove_flow_owner`,{environment:t,flow:i,principalId:o}))))})};export{c as registerOwnerReadTools,l as registerOwnerWriteTools};
5
+ //# sourceMappingURL=owners.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owners.js","names":[],"sources":["../../src/tools/owners.ts"],"sourcesContent":["/**\n * Owner/permission tools. Read: list_flow_owners. (Write owner tools — add/remove — are\n * appended by the write batch and gated behind ENABLE_WRITE_OPS.)\n */\n\nimport type { SomaServerInstance } from \"somamcp\"\nimport { z } from \"zod\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { appErrorToThrowable } from \"../errors.js\"\nimport {\n confirmWrite,\n DISCLAIMER,\n ensureWriteEnabled,\n type ReadToolOptions,\n renderResult,\n resolveEnvironment,\n type WriteToolOptions,\n} from \"./shared.js\"\n\nconst ENV_PARAM = z\n .string()\n .optional()\n .describe(\"Environment id (from list_environments). Omit to use the user's default environment.\")\n\nexport const registerOwnerReadTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: ReadToolOptions = {},\n): void => {\n server.addTool({\n name: \"list_flow_owners\",\n description: [\n \"List the owners/permissions on a flow.\",\n \"Returns: array of { principalId, principalType, roleName, principalDisplayName }.\",\n \"Parameters: flow (required), environment (optional).\",\n DISCLAIMER,\n 'Example: list_flow_owners { \"flow\": \"<guid>\" }',\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"List flow owners\" },\n parameters: z.object({\n environment: z\n .string()\n .optional()\n .describe(\"Environment id (from list_environments). Omit to use the user's default environment.\"),\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n }),\n execute: async ({ environment, flow }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.listFlowOwners(env, flow)),\n ),\n })\n}\n\nexport const registerOwnerWriteTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: WriteToolOptions,\n): void => {\n const gated = opts.enableWrite ? \"\" : \" [DISABLED: set ENABLE_WRITE_OPS=true]\"\n\n server.addTool({\n name: \"add_flow_owner\",\n description: [\n `Grant a principal a role on a flow (PUT .../permissions/{principalId})${gated}.`,\n \"Parameters: flow (required), principalId (required — AAD object id), roleName (CanEdit|CanView), environment (optional).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Add flow owner\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n principalId: z.string().describe(\"Azure AD object id of the user/group to grant.\"),\n roleName: z.enum([\"CanEdit\", \"CanView\"]).describe(\"Role to grant.\"),\n }),\n execute: async ({ environment, flow, principalId, roleName }) => {\n ensureWriteEnabled(opts.enableWrite, \"add_flow_owner\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.addFlowOwner(env, flow, principalId, roleName)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"add_flow_owner\", { environment: env, flow, principalId, roleName }),\n ),\n )\n },\n })\n\n server.addTool({\n name: \"remove_flow_owner\",\n description: [\n `Revoke a principal's access to a flow (DELETE .../permissions/{principalId})${gated}.`,\n \"Parameters: flow (required), principalId (required), environment (optional).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Remove flow owner\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n principalId: z.string().describe(\"Azure AD object id of the user/group to revoke.\"),\n }),\n execute: async ({ environment, flow, principalId }) => {\n ensureWriteEnabled(opts.enableWrite, \"remove_flow_owner\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.removeFlowOwner(env, flow, principalId)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"remove_flow_owner\", { environment: env, flow, principalId }),\n ),\n )\n },\n })\n}\n"],"mappings":"wMAoBA,MAAM,EAAY,EACf,OAAO,EACP,SAAS,EACT,SAAS,sFAAsF,EAErF,GACX,EACA,EACA,EAAwB,CAAC,IAChB,CACT,EAAO,QAAQ,CACb,KAAM,mBACN,YAAa,CACX,yCACA,oFACA,uDACA,EACA,gDACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,kBAAmB,EAC7D,WAAY,EAAE,OAAO,CACnB,YAAa,EACV,OAAO,EACP,SAAS,EACT,SAAS,sFAAsF,EAClG,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,CAC/D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,WAC5B,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,eAAe,EAAK,CAAI,CAAC,CACrE,CACJ,CAAC,CACH,EAEa,GACX,EACA,EACA,IACS,CACT,IAAM,EAAQ,EAAK,YAAc,GAAK,yCAEtC,EAAO,QAAQ,CACb,KAAM,iBACN,YAAa,CACX,yEAAyE,EAAM,GAC/E,2HACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,gBAAiB,EAC9D,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,YAAa,EAAE,OAAO,EAAE,SAAS,gDAAgD,EACjF,SAAU,EAAE,KAAK,CAAC,UAAW,SAAS,CAAC,EAAE,SAAS,gBAAgB,CACpE,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,cAAa,eAChD,EAAmB,EAAK,YAAa,gBAAgB,GAC7C,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,aAAa,EAAK,EAAM,EAAa,CAAQ,GAAG,KAC5D,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,iBAAkB,CAAE,YAAa,EAAK,OAAM,cAAa,UAAS,CAAC,CACxF,CACJ,EAEJ,CAAC,EAED,EAAO,QAAQ,CACb,KAAM,oBACN,YAAa,CACX,+EAA+E,EAAM,GACrF,+EACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,mBAAoB,EACjE,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,YAAa,EAAE,OAAO,EAAE,SAAS,iDAAiD,CACpF,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,kBACnC,EAAmB,EAAK,YAAa,mBAAmB,GAChD,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,gBAAgB,EAAK,EAAM,CAAW,GAAG,KACrD,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,oBAAqB,CAAE,YAAa,EAAK,OAAM,aAAY,CAAC,CACjF,CACJ,EAEJ,CAAC,CACH"}
@@ -0,0 +1,10 @@
1
+ import { FlowBackend } from "../backend/index.js";
2
+ import { ReadToolOptions, WriteToolOptions } from "./shared.js";
3
+ import { SomaServerInstance } from "somamcp";
4
+
5
+ //#region src/tools/runs.d.ts
6
+ declare const registerRunReadTools: (server: SomaServerInstance, backend: FlowBackend, opts?: ReadToolOptions) => void;
7
+ declare const registerRunWriteTools: (server: SomaServerInstance, backend: FlowBackend, opts: WriteToolOptions) => void;
8
+ //#endregion
9
+ export { registerRunReadTools, registerRunWriteTools };
10
+ //# sourceMappingURL=runs.d.ts.map
@@ -0,0 +1,6 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{DISCLAIMER as t,confirmWrite as n,ensureWriteEnabled as r,renderResult as i,resolveEnvironment as a}from"./shared.js";import{z as o}from"zod";const s=o.string().optional().describe(`Environment id (from list_environments). Omit to use the user's default environment.`),c=(n,r,c={})=>{n.addTool({name:`list_flow_runs`,description:[`List recent runs of a flow, newest first.`,"Returns: array of { name, status, startTime, endTime, durationMs, triggerName, error }. `name` is the run id used by get_flow_run.",`Parameters: flow (required), environment (optional), top (optional, default 20, max 100), status (optional: Succeeded/Failed/Running/Cancelled).`,t,`Example: list_flow_runs { "flow": "<guid>", "status": "Failed", "top": 10 }`].join(`
2
+ `),annotations:{readOnlyHint:!0,title:`List flow runs`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),top:o.number().int().min(1).max(100).optional().describe(`Max runs to return (default 20, max 100).`),status:o.enum([`Succeeded`,`Failed`,`Running`,`Cancelled`]).optional().describe(`Filter by run status.`)}),execute:async({environment:t,flow:n,top:o,status:s})=>(await a(r,c.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>i(await r.listRuns(e,n,{top:o,status:s})))}),n.addTool({name:`get_flow_run`,description:["Get one run in detail for debugging: status, timing, the triggering action, and the first-failure error code/message. The original run `properties` are included under `raw` (per-action breakdown lives there when the API provides it).",`Parameters: flow (required), run (required, from list_flow_runs), environment (optional).`,t,`Example: get_flow_run { "flow": "<guid>", "run": "<runId>" }`].join(`
3
+ `),annotations:{readOnlyHint:!0,title:`Get flow run detail`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),run:o.string().describe(`Run id (from list_flow_runs).`)}),execute:async({environment:t,flow:n,run:o})=>(await a(r,c.defaultEnvironment,t)).fold(t=>{throw e(t)},async e=>i(await r.getRun(e,n,o)))})},l=(i,c,l)=>{let u=l.enableWrite?``:` [DISABLED: set ENABLE_WRITE_OPS=true]`;i.addTool({name:`cancel_flow_run`,description:[`Cancel an in-progress run (POST .../runs/{run}/cancel)${u}.`,`Parameters: flow (required), run (required), environment (optional).`,t].join(`
4
+ `),annotations:{destructiveHint:!0,title:`Cancel flow run`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),run:o.string().describe(`Run id (from list_flow_runs).`)}),execute:async({environment:t,flow:i,run:o})=>(r(l.enableWrite,`cancel_flow_run`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.cancelFlowRun(t,i,o)).fold(t=>{throw e(t)},()=>n(`cancel_flow_run`,{environment:t,flow:i,run:o}))))}),i.addTool({name:`resubmit_flow_run`,description:[`Resubmit (replay) a run from its trigger (POST .../triggers/{trigger}/histories/{run}/resubmit)${u}.`,`Parameters: flow (required), run (required), trigger (required — trigger name from get_flow's triggers), environment (optional).`,t].join(`
5
+ `),annotations:{destructiveHint:!0,title:`Resubmit flow run`},parameters:o.object({environment:s,flow:o.string().describe(`Flow GUID name (from list_flows).`),run:o.string().describe(`Run id to resubmit (from list_flow_runs).`),trigger:o.string().describe("Trigger name (from get_flow's `triggers`).")}),execute:async({environment:t,flow:i,run:o,trigger:s})=>(r(l.enableWrite,`resubmit_flow_run`),(await a(c,l.defaultEnvironment,t)).fold(t=>{throw e(t)},async t=>(await c.resubmitFlowRun(t,i,s,o)).fold(t=>{throw e(t)},()=>n(`resubmit_flow_run`,{environment:t,flow:i,run:o,trigger:s}))))})};export{c as registerRunReadTools,l as registerRunWriteTools};
6
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.js","names":[],"sources":["../../src/tools/runs.ts"],"sourcesContent":["/**\n * Read-only run tools: list_flow_runs, get_flow_run (the debugging workhorse).\n */\n\nimport type { SomaServerInstance } from \"somamcp\"\nimport { z } from \"zod\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { appErrorToThrowable } from \"../errors.js\"\nimport {\n confirmWrite,\n DISCLAIMER,\n ensureWriteEnabled,\n type ReadToolOptions,\n renderResult,\n resolveEnvironment,\n type WriteToolOptions,\n} from \"./shared.js\"\n\nconst ENV_PARAM = z\n .string()\n .optional()\n .describe(\"Environment id (from list_environments). Omit to use the user's default environment.\")\n\nexport const registerRunReadTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: ReadToolOptions = {},\n): void => {\n server.addTool({\n name: \"list_flow_runs\",\n description: [\n \"List recent runs of a flow, newest first.\",\n \"Returns: array of { name, status, startTime, endTime, durationMs, triggerName, error }. `name` is the run id used by get_flow_run.\",\n \"Parameters: flow (required), environment (optional), top (optional, default 20, max 100), status (optional: Succeeded/Failed/Running/Cancelled).\",\n DISCLAIMER,\n 'Example: list_flow_runs { \"flow\": \"<guid>\", \"status\": \"Failed\", \"top\": 10 }',\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"List flow runs\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n top: z.number().int().min(1).max(100).optional().describe(\"Max runs to return (default 20, max 100).\"),\n status: z.enum([\"Succeeded\", \"Failed\", \"Running\", \"Cancelled\"]).optional().describe(\"Filter by run status.\"),\n }),\n execute: async ({ environment, flow, top, status }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.listRuns(env, flow, { top, status })),\n ),\n })\n\n server.addTool({\n name: \"get_flow_run\",\n description: [\n \"Get one run in detail for debugging: status, timing, the triggering action, and the first-failure error code/message. The original run `properties` are included under `raw` (per-action breakdown lives there when the API provides it).\",\n \"Parameters: flow (required), run (required, from list_flow_runs), environment (optional).\",\n DISCLAIMER,\n 'Example: get_flow_run { \"flow\": \"<guid>\", \"run\": \"<runId>\" }',\n ].join(\"\\n\"),\n annotations: { readOnlyHint: true, title: \"Get flow run detail\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n run: z.string().describe(\"Run id (from list_flow_runs).\"),\n }),\n execute: async ({ environment, flow, run }) =>\n (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) => renderResult(await backend.getRun(env, flow, run)),\n ),\n })\n}\n\nexport const registerRunWriteTools = (\n server: SomaServerInstance,\n backend: FlowBackend,\n opts: WriteToolOptions,\n): void => {\n const gated = opts.enableWrite ? \"\" : \" [DISABLED: set ENABLE_WRITE_OPS=true]\"\n\n server.addTool({\n name: \"cancel_flow_run\",\n description: [\n `Cancel an in-progress run (POST .../runs/{run}/cancel)${gated}.`,\n \"Parameters: flow (required), run (required), environment (optional).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Cancel flow run\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n run: z.string().describe(\"Run id (from list_flow_runs).\"),\n }),\n execute: async ({ environment, flow, run }) => {\n ensureWriteEnabled(opts.enableWrite, \"cancel_flow_run\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.cancelFlowRun(env, flow, run)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"cancel_flow_run\", { environment: env, flow, run }),\n ),\n )\n },\n })\n\n server.addTool({\n name: \"resubmit_flow_run\",\n description: [\n `Resubmit (replay) a run from its trigger (POST .../triggers/{trigger}/histories/{run}/resubmit)${gated}.`,\n \"Parameters: flow (required), run (required), trigger (required — trigger name from get_flow's triggers), environment (optional).\",\n DISCLAIMER,\n ].join(\"\\n\"),\n annotations: { destructiveHint: true, title: \"Resubmit flow run\" },\n parameters: z.object({\n environment: ENV_PARAM,\n flow: z.string().describe(\"Flow GUID name (from list_flows).\"),\n run: z.string().describe(\"Run id to resubmit (from list_flow_runs).\"),\n trigger: z.string().describe(\"Trigger name (from get_flow's `triggers`).\"),\n }),\n execute: async ({ environment, flow, run, trigger }) => {\n ensureWriteEnabled(opts.enableWrite, \"resubmit_flow_run\")\n return (await resolveEnvironment(backend, opts.defaultEnvironment, environment)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n async (env) =>\n (await backend.resubmitFlowRun(env, flow, trigger, run)).fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n () => confirmWrite(\"resubmit_flow_run\", { environment: env, flow, run, trigger }),\n ),\n )\n },\n })\n}\n"],"mappings":"wMAmBA,MAAM,EAAY,EACf,OAAO,EACP,SAAS,EACT,SAAS,sFAAsF,EAErF,GACX,EACA,EACA,EAAwB,CAAC,IAChB,CACT,EAAO,QAAQ,CACb,KAAM,iBACN,YAAa,CACX,4CACA,qIACA,mJACA,EACA,6EACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,gBAAiB,EAC3D,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,IAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,2CAA2C,EACrG,OAAQ,EAAE,KAAK,CAAC,YAAa,SAAU,UAAW,WAAW,CAAC,EAAE,SAAS,EAAE,SAAS,uBAAuB,CAC7G,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,MAAK,aACvC,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,SAAS,EAAK,EAAM,CAAE,MAAK,QAAO,CAAC,CAAC,CAChF,CACJ,CAAC,EAED,EAAO,QAAQ,CACb,KAAM,eACN,YAAa,CACX,4OACA,4FACA,EACA,8DACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,aAAc,GAAM,MAAO,qBAAsB,EAChE,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,IAAK,EAAE,OAAO,EAAE,SAAS,+BAA+B,CAC1D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,UAClC,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KACvE,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,IAAQ,EAAa,MAAM,EAAQ,OAAO,EAAK,EAAM,CAAG,CAAC,CAClE,CACJ,CAAC,CACH,EAEa,GACX,EACA,EACA,IACS,CACT,IAAM,EAAQ,EAAK,YAAc,GAAK,yCAEtC,EAAO,QAAQ,CACb,KAAM,kBACN,YAAa,CACX,yDAAyD,EAAM,GAC/D,uEACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,iBAAkB,EAC/D,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,IAAK,EAAE,OAAO,EAAE,SAAS,+BAA+B,CAC1D,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,UACnC,EAAmB,EAAK,YAAa,iBAAiB,GAC9C,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,cAAc,EAAK,EAAM,CAAG,GAAG,KAC3C,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,kBAAmB,CAAE,YAAa,EAAK,OAAM,KAAI,CAAC,CACvE,CACJ,EAEJ,CAAC,EAED,EAAO,QAAQ,CACb,KAAM,oBACN,YAAa,CACX,kGAAkG,EAAM,GACxG,mIACA,CACF,EAAE,KAAK;CAAI,EACX,YAAa,CAAE,gBAAiB,GAAM,MAAO,mBAAoB,EACjE,WAAY,EAAE,OAAO,CACnB,YAAa,EACb,KAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC,EAC7D,IAAK,EAAE,OAAO,EAAE,SAAS,2CAA2C,EACpE,QAAS,EAAE,OAAO,EAAE,SAAS,4CAA4C,CAC3E,CAAC,EACD,QAAS,MAAO,CAAE,cAAa,OAAM,MAAK,cACxC,EAAmB,EAAK,YAAa,mBAAmB,GAChD,MAAM,EAAmB,EAAS,EAAK,mBAAoB,CAAW,GAAG,KAC9E,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACA,KAAO,KACJ,MAAM,EAAQ,gBAAgB,EAAK,EAAM,EAAS,CAAG,GAAG,KACtD,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,MACM,EAAa,oBAAqB,CAAE,YAAa,EAAK,OAAM,MAAK,SAAQ,CAAC,CAClF,CACJ,EAEJ,CAAC,CACH"}
@@ -0,0 +1,29 @@
1
+ import { a as FlowApiError, t as AppError } from "../errors-BsWrc7px.js";
2
+ import { FlowBackend } from "../backend/index.js";
3
+ import { Either } from "functype";
4
+
5
+ //#region src/tools/shared.d.ts
6
+ declare const DISCLAIMER = "Uses Microsoft's unofficial api.flow.microsoft.com endpoint, which Microsoft labels unsupported; behavior may change.";
7
+ type ReadToolOptions = {
8
+ defaultEnvironment?: string;
9
+ };
10
+ type WriteToolOptions = ReadToolOptions & {
11
+ enableWrite: boolean;
12
+ };
13
+ /**
14
+ * Resolve the environment for a tool call: explicit parameter, else the configured
15
+ * DEFAULT_ENVIRONMENT, else the user's default environment discovered from the API.
16
+ */
17
+ declare const resolveEnvironment: (backend: FlowBackend, defaultEnv: string | undefined, explicit?: string) => Promise<Either<FlowApiError, string>>;
18
+ /** Render a backend result as a tool response: pretty JSON on success, throw on error. */
19
+ declare const renderResult: <E extends AppError, T>(result: Either<E, T>) => string;
20
+ /**
21
+ * Gate a mutating tool. Throws (before any backend call, so nothing mutates) with an
22
+ * actionable message when write ops are disabled.
23
+ */
24
+ declare const ensureWriteEnabled: (enableWrite: boolean, toolName: string) => void;
25
+ /** Confirmation payload for a successful mutation. */
26
+ declare const confirmWrite: (operation: string, details: Record<string, unknown>) => string;
27
+ //#endregion
28
+ export { DISCLAIMER, ReadToolOptions, WriteToolOptions, confirmWrite, ensureWriteEnabled, renderResult, resolveEnvironment };
29
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{appErrorToThrowable as e}from"../errors.js";import{Right as t}from"functype";const n=`Uses Microsoft's unofficial api.flow.microsoft.com endpoint, which Microsoft labels unsupported; behavior may change.`,r=async(e,n,r)=>r?t(r):n?t(n):e.resolveDefaultEnvironment(),i=t=>t.fold(t=>{throw e(t)},e=>JSON.stringify(e,null,2)),a=(e,t)=>{if(!e)throw Error(`write operations are disabled: "${t}" did not run and no changes were made. Set ENABLE_WRITE_OPS=true to enable mutating tools.`)},o=(e,t)=>JSON.stringify({ok:!0,operation:e,...t},null,2);export{n as DISCLAIMER,o as confirmWrite,a as ensureWriteEnabled,i as renderResult,r as resolveEnvironment};
2
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","names":[],"sources":["../../src/tools/shared.ts"],"sourcesContent":["/**\n * Helpers shared by every tool module: environment resolution and Either → tool-result\n * rendering. Keeping these here avoids repeating the fold/throw ceremony in each tool.\n */\n\nimport { type Either, Right } from \"functype\"\n\nimport type { FlowBackend } from \"../backend/index.js\"\nimport { type AppError, appErrorToThrowable, type FlowApiError } from \"../errors.js\"\n\nexport const DISCLAIMER =\n \"Uses Microsoft's unofficial api.flow.microsoft.com endpoint, which Microsoft labels unsupported; behavior may change.\"\n\nexport type ReadToolOptions = { defaultEnvironment?: string }\n\nexport type WriteToolOptions = ReadToolOptions & { enableWrite: boolean }\n\n/**\n * Resolve the environment for a tool call: explicit parameter, else the configured\n * DEFAULT_ENVIRONMENT, else the user's default environment discovered from the API.\n */\nexport const resolveEnvironment = async (\n backend: FlowBackend,\n defaultEnv: string | undefined,\n explicit?: string,\n): Promise<Either<FlowApiError, string>> => {\n if (explicit) return Right(explicit)\n if (defaultEnv) return Right(defaultEnv)\n return backend.resolveDefaultEnvironment()\n}\n\n/** Render a backend result as a tool response: pretty JSON on success, throw on error. */\nexport const renderResult = <E extends AppError, T>(result: Either<E, T>): string =>\n result.fold(\n (err) => {\n throw appErrorToThrowable(err)\n },\n (value) => JSON.stringify(value, null, 2),\n )\n\n/**\n * Gate a mutating tool. Throws (before any backend call, so nothing mutates) with an\n * actionable message when write ops are disabled.\n */\nexport const ensureWriteEnabled = (enableWrite: boolean, toolName: string): void => {\n if (!enableWrite) {\n throw new Error(\n `write operations are disabled: \"${toolName}\" did not run and no changes were made. Set ENABLE_WRITE_OPS=true to enable mutating tools.`,\n )\n }\n}\n\n/** Confirmation payload for a successful mutation. */\nexport const confirmWrite = (operation: string, details: Record<string, unknown>): string =>\n JSON.stringify({ ok: true, operation, ...details }, null, 2)\n"],"mappings":"oFAUA,MAAa,EACX,wHAUW,EAAqB,MAChC,EACA,EACA,IAEI,EAAiB,EAAM,CAAQ,EAC/B,EAAmB,EAAM,CAAU,EAChC,EAAQ,0BAA0B,EAI9B,EAAuC,GAClD,EAAO,KACJ,GAAQ,CACP,MAAM,EAAoB,CAAG,CAC/B,EACC,GAAU,KAAK,UAAU,EAAO,KAAM,CAAC,CAC1C,EAMW,GAAsB,EAAsB,IAA2B,CAClF,GAAI,CAAC,EACH,MAAU,MACR,mCAAmC,EAAS,4FAC9C,CAEJ,EAGa,GAAgB,EAAmB,IAC9C,KAAK,UAAU,CAAE,GAAI,GAAM,YAAW,GAAG,CAAQ,EAAG,KAAM,CAAC"}
@@ -0,0 +1,62 @@
1
+ //#region src/backend/types.d.ts
2
+ /**
3
+ * Clean domain models returned by the {@link FlowBackend} — the shapes tools serialize for
4
+ * the agent. Deliberately flatter and more stable than the raw api.flow.microsoft.com
5
+ * payloads, so a future Dataverse backend can produce the same types from a different source.
6
+ *
7
+ * Because the underlying API is unofficial and its payloads vary, the richer types keep a
8
+ * `raw` escape hatch carrying the original `properties` blob — nothing is lost while the
9
+ * clean shape stabilizes (see docs/api-notes.md).
10
+ */
11
+ type Environment = {
12
+ id: string;
13
+ name: string;
14
+ displayName: string;
15
+ location: string;
16
+ isDefault: boolean;
17
+ };
18
+ type FlowSummary = {
19
+ /** The flow's GUID name (used as the `flow` parameter everywhere else). */name: string;
20
+ displayName: string; /** "Started" | "Stopped" | "Suspended" (free-form: the API is not enum-stable). */
21
+ state: string;
22
+ createdTime: string;
23
+ lastModifiedTime: string; /** Creator principal (userId or email) when present. */
24
+ owner?: string;
25
+ };
26
+ type FlowDetail = FlowSummary & {
27
+ /** The workflow definition JSON (triggers + actions). */definition?: unknown;
28
+ connectionReferences?: unknown; /** Trigger names parsed from the definition. */
29
+ triggers: string[]; /** Action names parsed from the definition. */
30
+ actions: string[];
31
+ };
32
+ type RunStatus = "Succeeded" | "Failed" | "Running" | "Cancelled";
33
+ type RunSummary = {
34
+ /** The run's id (used as the `run` parameter). */name: string;
35
+ status: string;
36
+ startTime: string;
37
+ endTime?: string;
38
+ durationMs?: number;
39
+ triggerName?: string; /** Short error message when the run failed. */
40
+ error?: string;
41
+ };
42
+ type RunDetail = RunSummary & {
43
+ /** Error code/message detail surfaced for the first failure, when present. */errorCode?: string; /** Original `properties` blob — per-action breakdown lives here when the API includes it. */
44
+ raw?: unknown;
45
+ };
46
+ type Connection = {
47
+ name: string;
48
+ apiName: string;
49
+ displayName: string; /** "Connected" when healthy; anything else (e.g. "Error") signals a broken connection. */
50
+ status: string;
51
+ accountName?: string;
52
+ expiresAt?: string;
53
+ };
54
+ type FlowOwner = {
55
+ principalId: string;
56
+ principalType: string;
57
+ roleName: string;
58
+ principalDisplayName?: string;
59
+ };
60
+ //#endregion
61
+ export { FlowSummary as a, RunSummary as c, FlowOwner as i, Environment as n, RunDetail as o, FlowDetail as r, RunStatus as s, Connection as t };
62
+ //# sourceMappingURL=types-DRNRhdJl.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { AccountInfo } from "@azure/msal-node";
2
+
3
+ //#region src/auth/types.d.ts
4
+ type AuthMode = "interactive" | "clientCredentials";
5
+ /** Resource/audience for the Power Automate management API. */
6
+ declare const FLOW_RESOURCE: "https://service.flow.microsoft.com/";
7
+ /**
8
+ * Ordered scope candidates to try for the Flow audience under a public client.
9
+ * The double-slash form appears in several Microsoft samples and is intentional.
10
+ */
11
+ declare const FLOW_SCOPE_CANDIDATES: ReadonlyArray<ReadonlyArray<string>>;
12
+ type TokenSet = {
13
+ readonly accessToken: string; /** Epoch milliseconds. */
14
+ readonly expiresAt: number; /** The scope set that actually minted this token (pinned for reuse + diagnostics). */
15
+ readonly scopeUsed: ReadonlyArray<string>; /** MSAL account, needed for silent refresh in interactive mode. */
16
+ readonly account?: AccountInfo;
17
+ };
18
+ type TransportKind = "stdio" | "http";
19
+ type TelemetrySink = "console" | "file";
20
+ type ServerConfig = {
21
+ readonly clientId: string;
22
+ readonly tenantId: string;
23
+ readonly authMode: AuthMode;
24
+ readonly clientSecret?: string;
25
+ readonly flowResource: string; /** Primary scope set; defaults to `<flowResource>.default`. Overridable via FLOW_SCOPES. */
26
+ readonly flowScopes: ReadonlyArray<string>;
27
+ readonly tokenCachePath: string;
28
+ readonly transport: TransportKind;
29
+ readonly port: number;
30
+ readonly endpoint: `/${string}`; /** Bearer token that protects the HTTP operational endpoints (health/info/dashboard). */
31
+ readonly mcpApiKey?: string;
32
+ readonly enableWriteOps: boolean;
33
+ readonly defaultEnvironment?: string;
34
+ readonly telemetry: ReadonlyArray<TelemetrySink>;
35
+ readonly telemetryFilePath: string;
36
+ readonly logLevel: string;
37
+ readonly feedbackRepo: string;
38
+ };
39
+ //#endregion
40
+ export { TelemetrySink as a, ServerConfig as i, FLOW_RESOURCE as n, TokenSet as o, FLOW_SCOPE_CANDIDATES as r, TransportKind as s, AuthMode as t };
41
+ //# sourceMappingURL=types-HGbUI9Dc.d.ts.map
@@ -0,0 +1,9 @@
1
+ //#region src/version.d.ts
2
+ /**
3
+ * Single source of truth for the server version reported to SomaMCP (which requires the
4
+ * strict `${number}.${number}.${number}` shape). Keep in sync with package.json on release.
5
+ */
6
+ declare const PKG_VERSION: "0.1.0";
7
+ //#endregion
8
+ export { PKG_VERSION };
9
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,2 @@
1
+ const e=`0.1.0`;export{e as PKG_VERSION};
2
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["/**\n * Single source of truth for the server version reported to SomaMCP (which requires the\n * strict `${number}.${number}.${number}` shape). Keep in sync with package.json on release.\n */\nexport const PKG_VERSION = \"0.1.0\" as const\n"],"mappings":"AAIA,MAAa,EAAc"}