Skip to content
Back to skills

Cloud Flows

ASecurity

Work with Power Automate solution-aware Cloud Flows on Dataverse — inspect, save draft, publish, restore versions, trigger runs, debug action outputs, and patch FetchXML / action inputs surgically. Includes a compact iteration workflow for safe edit-test-publish cycles, and FetchXML guidance (rollup trade-offs, comment pitfalls, aggregate limits) for classification flows. Use when the user asks about Power Automate cloud flows in a Dataverse environment.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 22, 2026
ai-agentsgoc#shellnodeexpressapidocumentation

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 22, 2026

npx -y skills add hydr/dataverse-modelling-mcp --skill cloud-flows --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cloud Flows?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cloud Flows
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hydr-cloud-flows/badge)](https://www.skillsdirectory.com/skills/hydr-cloud-flows)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cloud-flows
description: Work with Power Automate solution-aware Cloud Flows on Dataverse — inspect, save draft, publish, restore versions, trigger runs, debug action outputs, and patch FetchXML / action inputs surgically. Includes a compact iteration workflow for safe edit-test-publish cycles, and FetchXML guidance (rollup trade-offs, comment pitfalls, aggregate limits) for classification flows. Use when the user asks about Power Automate cloud flows in a Dataverse environment.
version: 1.1
---

# Cloud Flows on Dataverse

Expert reference for managing **solution-aware** Power Automate cloud flows via the dataverse-modelling-mcp server.

## Hard prerequisites

- **Only solution-aware flows have version history.** Flows in the default solution get no `componentversion` rows. If a flow is missing from version-history queries, check that it belongs to a non-default solution (`solutioncomponents` filtered on `objectid eq <flowid>`).
- **Environment write policy:** `contoso-dev` is the safe write target. `contoso-prod` (production) and `contoso-staging` need explicit user confirmation before any mutating call.
- **The `componentversion` virtual entity refuses direct Create.** The Microsoft Learn doc lists Create as supported, but every POST/PATCH against `/componentversions` or the elastic backing table `/componentversionnrddatasourceset` fails. The bound actions documented below are the only working write path.

## Tool map

| User intent | MCP tool | Underlying call |
|---|---|---|
| List flows | `flow_list` | Power Automate Flow API |
| Inspect single flow | `flow_get`, `flow_describe` | Power Automate Flow API |
| **Get raw clientdata (with `connectionReferences` wrapper)** | `flow_get_clientdata` | Dataverse `GET /workflows({id})?$select=clientdata` |
| **Create solution-aware flow (version history + draft/publish)** | `flow_create(…, solutionUniqueName=…)` | Dataverse `POST /workflows` (category=5, type=1) |
| Create personal flow (no versioning) | `flow_create(…)` without `solutionUniqueName` | Power Automate Flow API |
| List version history | `flow_list_versions` | Dataverse `GET /workflows({id})/componentversions` |
| Inspect single version | `flow_get_version` | Filtered list (direct GET-by-key is blocked) |
| **Save a draft (Op=1 Update version)** | `flow_save_draft` | `PATCH /workflows({id})` + header `mscrm.AsUnpublished: true` |
| **Publish current draft (Op=2 Publish version)** | `flow_publish` | `POST /PublishComponent?ActivateFlowOnPublish=<bool>` |
| **Save draft + publish in one call** | `flow_save_draft_and_publish` | combined PATCH + PublishComponent |
| **Surgical update of one action input** | `flow_patch_action_input` | GET clientdata → mutate one key under `actions.<X>.inputs.parameters` → save draft (optional publish) |
| **Validate a FetchXML against Dataverse** | `fetchxml_validate` | `GET /{entitySet}?fetchXml=<xml>` read-only |
| Activate/deactivate at runtime (no snapshot) | `flow_set_state` | Power Automate `/start` or `/stop` |
| **Restore a prior published version (Op=3 Restore)** | `flow_restore_version` | `POST /workflows({id})/Microsoft.Dynamics.CRM.RestoreComponentVersion` |
| Get run history | `flow_get_runs` | Power Automate Flow API |
| **Trigger a flow run** | `flow_trigger_run` | `POST .../triggers/{triggerName}/run` |
| **Wait until a run reaches a terminal state** | `flow_wait_for_run` | Polls `GET .../runs/{runId}` until status != Running |
| **List actions of a single run (status + outputsLink)** | `flow_get_run_actions` | `GET .../runs/{runId}/actions` |
| **Fetch the raw outputs of one action** | `flow_get_action_outputs` | resolves SAS-signed outputsLink — no Bearer header |

## Conceptual model

The `workflow` row carries the **live** flow. Alongside it, the virtual `componentversion` view exposes snapshots. Each snapshot has an `Operation` discriminator:

| Operation | Label | Triggered by |
|---|---|---|
| 0 | Create | Initial flow creation |
| 1 | Update | `flow_save_draft` (PATCH with `mscrm.AsUnpublished: true`) |
| 2 | Publish | `flow_publish` (POST `PublishComponent`) |
| 3 | Restore | `flow_restore_version` |
| 4 | Solution Import | Solution-import pipeline |

**Restore semantics:** `RestoreComponentVersion` only acts on **Publish-typed (Op=2)** versions. Calls against Create/Update IDs return HTTP 204 but produce no observable change.

**Publish vs. Activate:** independent. `flow_publish` snapshots the draft as the new published version but leaves the runtime state unchanged. `ActivateFlowOnPublish=true` combines both. `flow_set_state` toggles runtime alone without touching version history.

## Solution-flow patch & test workflow

The full iteration cycle for a safe edit on `contoso-dev`, with MCP tools alone (no PowerShell escape hatches required for the common path):

1. **Inspect** — `flow_get` to confirm the action name and current shape. For full `clientdata` (incl. `connectionReferences`), `flow_get_clientdata`. `flow_list_versions(flowId)` to note the last good Op=2 version-id as rollback anchor.
2. **Edit** — choose the smallest path that fits:
   - One parameter on one action (most edits): `flow_patch_action_input(flowId, actionName, parameterName, valueJson, publish=false)`. Avoids round-tripping the full clientdata.
   - Multi-action / structural change: `flow_get_clientdata` → mutate locally → `flow_save_draft` (or `flow_save_draft_and_publish`).
3. **Pre-flight FetchXML changes** — `fetchxml_validate(entitySet, fetchXml, sampleSize)` against the active org before patching it into a List action. Catches well-formedness errors (e.g. `--` in comments) and reports the row count to be classified.
4. **Publish** — `flow_publish(flowId, activateFlow=false)` (or pass `publish=true` directly to `flow_patch_action_input`). Confirm via `flow_list_versions` that a new Op=2 row landed and `connectionReferences` is still populated.
5. **Trigger** — `flow_trigger_run(flowId, triggerName)`. For Recurrence-triggered flows, `triggerName="Recurrence"` is the canonical name.
6. **Wait** — `flow_wait_for_run(flowId, runId, timeoutSeconds=300)`. Blocks server-side until terminal state.
7. **Validate the run** — `flow_get_run_actions(flowId, runId)` lists every action's status; for any `Failed` row, follow its `OutputsLink` via `flow_get_action_outputs(flowId, runId, actionName)` to get the error body (Dataverse error code, BadRequest message, etc.). For Compose-style telemetry actions, fetch the same way and inspect the produced JSON / HTML.
8. **Rollback if needed** — `flow_restore_version(flowId, <lastGoodOp2Id>)` then `flow_publish`. Restore produces an Op=3 row pointing at the source for audit.

Single-action edits commonly compress to two tool calls: `fetchxml_validate` (read-only) followed by `flow_patch_action_input(..., publish=true)`.

## FetchXML inside classification / lifecycle flows

When a flow drives lifecycle decisions via FetchXML against `accounts` / `contacts` etc., a handful of dataverse-specific traps recur. Each carries a trade-off — none of these is a hard ban, but the consequences should be conscious.

### Rollup-field reliance — convenient but lagged

Dataverse rollup fields like `sample_open_opportunities` or `sample_number_orders` are **asynchronously recomputed**. Real lag observed: minutes to hours, sometimes longer for cold accounts. They can be stale in either direction:
- A new opportunity exists but `sample_open_opportunities` still shows `0`.
- A record's rollup was never computed → the field is **`null`**, not `0`. `condition operator='eq' value='0'` then matches nothing.

**Trade-off**: rollups are cheap (single attribute, no join), and for thresholds like `eq 1` or `ge 2` they are essentially the only practical option (see below). For *existence* checks they can quietly mis-classify records.

**When to prefer an outer-join existence check** (`<link-entity ... link-type='outer' alias='X'>` + `condition entityname='X' attribute='<id>' operator='null'/'not-null'`):
- The check is the *only thing* gating a destructive / overwriting classification (e.g. „has zero open opps" used to mark `Verlorener Potentialkunde`).
- The records are recent / volatile (frequently changing leads, opps, orders).
- A wrong classification has measurable downstream cost.

**When the rollup is fine**:
- Threshold semantics that an existence-check cannot express (`exactly 1`, `>= 2`).
- A higher-priority phase in the same flow will overwrite an incorrect classification anyway (e.g. `A` after `N` covers stale `sample_number_orders eq 1` for accounts that actually have 2+ orders).

### Aggregate FetchXML — no cross-entity HAVING

FetchXML supports `aggregate='true'` with `groupby` and `aggregate='countcolumn'` on a linked entity. Documentation suggests aggregate aliases can be used as filters. **In practice, an aggregate-alias condition on a cross-entity outer-join is rejected** by Dataverse:

```xml
<!-- This is "Attribute not found in XNode" or value-coerced-to-GUID error -->
<filter><condition alias='ordercount' operator='ge' value='2' /></filter>
```

What works:
- Boolean existence on the outer-join: `<condition entityname='oa' attribute='salesorderid' operator='not-null' />` (≥1) or `'null'` (=0).
- Numeric threshold via the rollup field on the parent entity itself (`<condition attribute='sample_number_orders' operator='ge' value='2' />`) — same trade-off as above.
- Aggregate-alias HAVING on the **same entity** (no joins) — that case works as documented.

For „exactly N" or „at least N≥2" cross-entity, you either accept the rollup-field dependency or accept a structural workaround (e.g. two outer-joins with disjoint filters to prove ≥2 distinct rows, at the cost of edge cases that match neither).

### XML-comment pitfall: `--` is invalid

Per W3C, an XML comment may not contain `--`. Dataverse parses FetchXML strictly:

```xml
<!-- Verlorener Potentialkunde (772600007)  -- NEU -->  <!-- ❌ rejected: "Invalid XML." -->
```

Easy to overlook in dash-separated section headers. `fetchxml_validate` catches this pre-flight.

### Sending emails from a flow (Dataverse connector)

The Dataverse connector supports inline activity parties on email creation — via a specific op/parameter combination:

- Use `operationId: CreateRecordWithOrganization` (**not** `CreateRecord`) and pass `organization: "current"` in parameters.
- The parameter is named `item/activitypointer_activity_parties` (**not** `item/email_activity_parties`). The latter triggers `InvalidOpenApiFlow / WorkflowOperationParametersExtraParameter`.
- Inline array shape:
  ```jsonc
  "item/activitypointer_activity_parties": [
    { "participationtypemask": 1, "partyid@odata.bind": "/systemusers(<sender-guid>)" },
    { "participationtypemask": 2, "partyid@odata.bind": "/systemusers(<recipient-guid>)" },
    { "participationtypemask": 2, "addressused": "team@example.com" }
  ]
  ```
  `addressused` works for distribution lists / shared mailboxes without resolving a systemuser.
- Send via `PerformBoundAction`, `actionName: Microsoft.Dynamics.CRM.SendEmail`, `item/IssueSend: true` (false = mark sent in CRM without SMTP delivery).
- Reference flow (example): a scheduled nightly cloud flow, workflowid `50000000-0000-0000-0000-000000000006`.

## Reverse-engineered endpoints (Maker UI parity)

These came from Playwright network traces of `make.powerautomate.com` and are now wrapped by the MCP tools. Documented here for the rare case the wrapper needs extending — most work belongs in the service layer, not in tool callers.

### Restore (bound action on workflow)
```http
POST /api/data/v9.2/workflows({flowId})/Microsoft.Dynamics.CRM.RestoreComponentVersion
Content-Type: application/json

{"RestoringVersionId": "componentversions(<componentversionId>)"}
```
- Parameter name is **PascalCase**. A C# anonymous object `new { RestoringVersionId = ... }` gets serialized as `restoringVersionId` by the shared `JsonSerializerOptions` (CamelCase policy) and rejected. Use a `Dictionary<string, object?>`.
- Returns 204 even when the snapshot wasn't actually applied — verify via re-list.

### Publish (unbound action)
```http
POST /api/data/v9.2/PublishComponent?ActivateFlowOnPublish=false
Content-Type: application/json

{"Target": "/workflows({flowId})"}
```

### Save draft (direct PATCH with special header)
```http
PATCH /api/data/v9.2/workflows({flowId})
Content-Type: application/json
mscrm.AsUnpublished: true
If-Match: *

{"clientdata": "<stringified-logic-apps-json>", "name": "..."}
```
- Without `mscrm.AsUnpublished`, the PATCH may try to validate/publish immediately and fail.
- A second header `mscrm.SkipComponentVersioning: true` is what the Maker UI uses in the publish-batch's first PATCH to avoid double-versioning; do **not** add it to plain Save-draft calls — it would suppress the Update version row entirely.

### Manual run (PA Flow API)
```http
POST https://{region}.api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/{envId}/flows/{flowId}/triggers/{triggerName}/run?api-version=2016-11-01
```
- Returns 200 with no usable body. Read the new RunId by listing runs (`/runs?$top=1`) right after.

### Action outputs (per-action endpoint — stable)
```http
GET .../flows/{flowId}/runs/{runId}/actions/{actionName}?api-version=2016-11-01
```
- Returns `properties.outputsLink.uri` — a SAS-signed URL. Download it **without** a Bearer header; otherwise `DirectApiRequestHasMoreThanOneAuthorization`.
- Prefer this over `GET .../runs/{runId}?$expand=properties/actions`, which intermittently returns ASP.NET runtime-error HTML.

## General pitfalls

- **`clientdata` is a stringified JSON**, not nested JSON. When constructing it manually, the inner `properties.definition` block must use Logic Apps keys `$schema`, `$connections`, `$authentication` — verbatim with dollar signs. Don't try to coerce them via `string.Replace`; build the JSON literal directly (raw string interpolation in C# works well).
- **Manual trigger schema:** the Request trigger's `inputs.schema` must be a JSON-schema object (`{"type":"object","properties":{},"required":[]}`), not an empty `{}`.
- **Direct OData PATCH on `clientdata` bypasses versioning.** It modifies the live flow without creating a `componentversion` row. To get a proper Update snapshot, go through `flow_save_draft` (or any wrapper that includes the `mscrm.AsUnpublished: true` header).
- **Composite key lookups are blocked:** `GET /componentversions({id})` and `GET /workflows({wfid})/componentversions({verId})` both fail with "Get with NavigationKey is allowed only on Metadata Entities." Resolve a single version by listing and filtering client-side.
- **`RestoreComponentVersion` is async.** After calling it, give Dataverse 1–2 seconds before re-listing versions to assert the Op=3 row exists.
- **External PA-MCP `update_flow` corrupts solution-aware flows.** It transforms `connectionReferences` into the flat Flow-API shape and loses `connectionReferenceLogicalName`, leaving every `OpenApiConnection` action broken. Always use the dataverse-modelling-mcp save-draft path for solution-aware flows.

## Typical workflows

### "Roll a flow back to yesterday's published version"
1. `flow_list_versions(flowId)` → identify the target row where `Operation == 2` and `CreatedOn` matches.
2. `flow_restore_version(flowId, versionId)` — produces a new Op=3 row and a fresh Draft on the workflow.
3. (Optional) `flow_publish(flowId, activateFlow=true)` to re-publish-and-activate immediately.

### "Replace the FetchXML of one List action"
1. `fetchxml_validate(entitySet="accounts", fetchXml=<new>, sampleSize=3)` — well-formed? row count plausible?
2. `flow_patch_action_input(flowId, actionName="List_Lost_Customers", parameterName="fetchXml", valueJson="\"<the new xml>\"", publish=true)` — saves draft and publishes in one go.
3. `flow_trigger_run(flowId, "Recurrence")` → `flow_wait_for_run(flowId, runId)` → `flow_get_run_actions(flowId, runId)`.

### "Apply a small edit safely without breaking the active flow"
1. `flow_get(flowId)` to confirm structure (or `flow_get_clientdata` for the full wrapper).
2. `flow_patch_action_input(...)` (no publish) — creates Op=1 Update version, runtime unaffected.
3. Inspect & test via run, then `flow_publish(flowId)` when ready.

### "Disable a noisy flow temporarily"
- `flow_set_state(flowId, enable=false)` — stops runs without changing version history. Re-enable later with `enable=true`.

### "Audit who changed what"
- `flow_list_versions(flowId)` returns each row with `CreatedByName`, `Operation`, `CreatedOn`.

## Related memories

- See [[feedback_environment_write_policy]] for the contoso-dev vs contoso-prod/contoso-staging write rules.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.