Files
awesome-copilot/skills/flowstudio-power-automate-build/references/action-patterns-connectors.md
T
Catherine Han e67c66c441 Update FlowStudio Power Automate skills (#1664)
* feat(flowstudio): align Power Automate skills with MCP server v1.1.6

Foundation skill (flowstudio-power-automate-mcp) rewritten to use the
server's new tool_search and list_skills meta-tools (v1.1.5+) for
discovery instead of cataloging every tool by hand. Cut from 519 to
295 lines. New "Which Skill to Use When" intent-keyed decision tree
points at the four specialized skills.

Build/debug/governance/monitoring updated for use-case framing. Tools
that genuinely cross tiers (e.g. debug skill borrowing
get_store_flow_summary) are correct when the workflow needs them — the
split between skills is by use-case intent, not by tool partition.

Build skill: new Step 3a Resolving Dynamic Connector Values covers
get_live_dynamic_options outer-parameter auto-bridge (v1.1.6+) and the
AadGraph user-picker fallback via shared_office365users.SearchUserV2
(replaces broken builtInOperation:AadGraph.GetUsers).

Debug skill: Outlook user-picker failure note pointing at the fallback.

Monitoring skill description disambiguates from the server's monitor-flow
tool bundle (runtime control of a single flow) — this skill is
tenant-wide health analytics over the cached store.

All 5 skills validate via npm run skill:validate; line endings LF only;
codespell clean; auto-regenerated docs/README.skills.md included.

* fix(flowstudio): remove deprecated tool references

The v1.1.5 MCP server release marked 5 tools [DEPRECATED] but the
previous alignment commit missed them. Replacements per server source:

- get_live_flow_http_schema → read trigger.inputs.schema from get_live_flow
- get_live_flow_trigger_url → read trigger.metadata.callbackUrl from get_live_flow
- get_store_flow_trigger_url → get_store_flow.triggerUrl field
- get_store_flow_errors → get_store_flow_runs(status=["Failed"])
- set_store_flow_state → set_live_flow_state

Touches build, debug, governance, monitoring SKILL.md and the
foundation skill's tool-reference.md. Remaining mentions of the
deprecated names are intentional — they live in deprecation notices
naming the obsolete wrapper alongside its replacement.

* Update FlowStudio Power Automate skills

* Cover latest FlowStudio MCP actions

* Trim FlowStudio Power Automate skills

* Number FlowStudio build workflow steps
2026-05-11 11:28:29 +10:00

18 KiB

FlowStudio MCP — Action Patterns: Connectors

SharePoint, Outlook, Teams, and Approvals connector action patterns.

All examples assume "runAfter" is set appropriately. Replace <connectionName> with the key you used in connectionReferences (e.g. shared_sharepointonline, shared_teams). This is NOT the connection GUID — it is the logical reference name that links the action to its entry in the connectionReferences map.


SharePoint

SharePoint — Get Items

"Get_SP_Items": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItems"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "$filter": "Status eq 'Active'",
      "$top": 500
    }
  }
}

Result reference: @outputs('Get_SP_Items')?['body/value']

Dynamic OData filter with string interpolation: inject a runtime value directly into the $filter string using @{...} syntax:

"$filter": "Title eq '@{outputs('ConfirmationCode')}'"  

Note the single-quotes inside double-quotes — correct OData string literal syntax. Avoids a separate variable action.

Pagination for large lists: by default, GetItems stops at $top. To auto-paginate beyond that, enable the pagination policy on the action. In the flow definition this appears as:

"paginationPolicy": { "minimumItemCount": 10000 }

Set minimumItemCount to the maximum number of items you expect. The connector will keep fetching pages until that count is reached or the list is exhausted. Without this, flows silently return a capped result on lists with >5,000 items.


SharePoint — Get Item (Single Row by ID)

"Get_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "id": "@triggerBody()?['ID']"
    }
  }
}

Result reference: @body('Get_SP_Item')?['FieldName']

Use GetItem (not GetItems with a filter) when you already have the ID. Re-fetching after a trigger gives you the current row state, not the snapshot captured at trigger time — important if another process may have modified the item since the flow started.


SharePoint — Create Item

"Create_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "PostItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "item/Title": "@variables('myTitle')",
      "item/Status": "Active"
    }
  }
}

SharePoint — Update Item

"Update_SP_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "PatchItem"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "MyList",
      "id": "@item()?['ID']",
      "item/Status": "Processed"
    }
  }
}

PatchItem can validate required SharePoint columns even when you are not changing those fields. Echo unchanged required fields from the trigger or a prior Get Item action, for example item/Title, and use internal field names.


SharePoint — File Upsert (Create or Overwrite in Document Library)

SharePoint's CreateFile fails if the file already exists. To upsert (create or overwrite) without a prior existence check, use GetFileMetadataByPath on both Succeeded and Failed from CreateFile — if create failed because the file exists, the metadata call still returns its ID, which UpdateFile can then overwrite:

"Create_File": {
  "type": "OpenApiConnection",
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "CreateFile" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "folderPath": "/My Library/Subfolder",
      "name": "@{variables('filename')}",
      "body": "@outputs('Compose_File_Content')"
    }
  }
},
"Get_File_Metadata_By_Path": {
  "type": "OpenApiConnection",
  "runAfter": { "Create_File": ["Succeeded", "Failed"] },
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "GetFileMetadataByPath" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "path": "/My Library/Subfolder/@{variables('filename')}"
    }
  }
},
"Update_File": {
  "type": "OpenApiConnection",
  "runAfter": { "Get_File_Metadata_By_Path": ["Succeeded", "Skipped"] },
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
              "connectionName": "<connectionName>", "operationId": "UpdateFile" },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "id": "@outputs('Get_File_Metadata_By_Path')?['body/{Identifier}']",
      "body": "@outputs('Compose_File_Content')"
    }
  }
}

If Create_File succeeds, Get_File_Metadata_By_Path is Skipped and Update_File still fires (accepting Skipped), harmlessly overwriting the file just created. If Create_File fails (file exists), the metadata call retrieves the existing file's ID and Update_File overwrites it. Either way you end with the latest content.

Document library system properties — when iterating a file library result (e.g. from ListFolder or GetFilesV2), use curly-brace property names to access SharePoint's built-in file metadata. These are different from list field names:

@item()?['{Name}']                  — filename without path (e.g. "report.csv")
@item()?['{FilenameWithExtension}'] — same as {Name} in most connectors
@item()?['{Identifier}']            — internal file ID for use in UpdateFile/DeleteFile
@item()?['{FullPath}']              — full server-relative path
@item()?['{IsFolder}']             — boolean, true for folder entries

SharePoint — GetItemChanges Column Gate

When a SharePoint "item modified" trigger fires, it doesn't tell you WHICH column changed. Use GetItemChanges to get per-column change flags, then gate downstream logic on specific columns:

"Get_Changes": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetItemChanges"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "table": "<list-guid>",
      "id": "@triggerBody()?['ID']",
      "since": "@triggerBody()?['Modified']",
      "includeDrafts": false
    }
  }
}

Gate on a specific column:

"expression": {
  "and": [{
    "equals": [
      "@body('Get_Changes')?['Column']?['hasChanged']",
      true
    ]
  }]
}

New-item detection: On the very first modification (version 1.0), GetItemChanges may report no prior version. Check @equals(triggerBody()?['OData__UIVersionString'], '1.0') to detect newly created items and skip change-gate logic for those.


SharePoint — REST MERGE via HttpRequest

For cross-list updates or advanced operations not supported by the standard Update Item connector (e.g., updating a list in a different site), use the SharePoint REST API via the HttpRequest operation:

"Update_Cross_List_Item": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "HttpRequest"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/target-site",
      "parameters/method": "POST",
      "parameters/uri": "/_api/web/lists(guid'<list-guid>')/items(@{variables('ItemId')})",
      "parameters/headers": {
        "Accept": "application/json;odata=nometadata",
        "Content-Type": "application/json;odata=nometadata",
        "X-HTTP-Method": "MERGE",
        "IF-MATCH": "*"
      },
      "parameters/body": "{ \"Title\": \"@{variables('NewTitle')}\", \"Status\": \"@{variables('NewStatus')}\" }"
    }
  }
}

Key headers:

  • X-HTTP-Method: MERGE — tells SharePoint to do a partial update (PATCH semantics)
  • IF-MATCH: * — overwrites regardless of current ETag (no conflict check)

The HttpRequest operation reuses the existing SharePoint connection — no extra authentication needed. Use this when the standard Update Item connector can't reach the target list (different site collection, or you need raw REST control). Keep the connector-specific parameter names exactly as shown: parameters/method, parameters/uri, parameters/headers, and parameters/body. The body is a JSON string, and parameters/uri is relative to the SharePoint dataset.


SharePoint — File as JSON Database (Read + Parse)

Use a SharePoint document library JSON file as a queryable "database" of last-known-state records. A separate process (e.g., Power BI dataflow) maintains the file; the flow downloads and filters it for before/after comparisons.

"Get_File": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "connectionName": "<connectionName>",
      "operationId": "GetFileContent"
    },
    "parameters": {
      "dataset": "https://mytenant.sharepoint.com/sites/mysite",
      "id": "%252fShared%2bDocuments%252fdata.json",
      "inferContentType": false
    }
  }
},
"Parse_JSON_File": {
  "type": "Compose",
  "runAfter": { "Get_File": ["Succeeded"] },
  "inputs": "@json(decodeBase64(body('Get_File')?['$content']))"
},
"Find_Record": {
  "type": "Query",
  "runAfter": { "Parse_JSON_File": ["Succeeded"] },
  "inputs": {
    "from": "@outputs('Parse_JSON_File')",
    "where": "@equals(item()?['id'], variables('RecordId'))"
  }
}

Decode chain: GetFileContent returns base64-encoded content in body(...)?['$content']. Apply decodeBase64() then json() to get a usable array. Filter Array then acts as a WHERE clause.

When to use: When you need a lightweight "before" snapshot to detect field changes from a webhook payload (the "after" state). Simpler than maintaining a full SharePoint list mirror — works well for up to ~10K records.

File path encoding: In the id parameter, SharePoint URL-encodes paths twice. Spaces become %2b (plus sign), slashes become %252f.


Excel Online

Excel — Run Office Script

Office Script actions require real workbook and script identifiers at save time. Do not deploy placeholder scriptId values; update_live_flow can fail during dynamic operation validation even before a test run exists.

Use describe_live_connector or get_live_dynamic_options when available, or ask the user for the workbook and script if they are not discoverable. If a real scriptId still cannot be resolved, ask the user to add the Run script action once in the designer, then read the flow definition and preserve the resolved parameters.


Outlook

Outlook — Send Email

"Send_Email": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
      "connectionName": "<connectionName>",
      "operationId": "SendEmailV2"
    },
    "parameters": {
      "emailMessage/To": "recipient@contoso.com",
      "emailMessage/Subject": "Automated notification",
      "emailMessage/Body": "<p>@{outputs('Compose_Message')}</p>",
      "emailMessage/IsHtml": true
    }
  }
}

Outlook — Get Emails (Read Template from Folder)

"Get_Email_Template": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365",
      "connectionName": "<connectionName>",
      "operationId": "GetEmailsV3"
    },
    "parameters": {
      "folderPath": "Id::<outlook-folder-id>",
      "fetchOnlyUnread": false,
      "includeAttachments": false,
      "top": 1,
      "importance": "Any",
      "fetchOnlyWithAttachment": false,
      "subjectFilter": "My Email Template Subject"
    }
  }
}

Access subject and body:

@first(outputs('Get_Email_Template')?['body/value'])?['subject']
@first(outputs('Get_Email_Template')?['body/value'])?['body']

Outlook-as-CMS pattern: store a template email in a dedicated Outlook folder. Set fetchOnlyUnread: false so the template persists after first use. Non-technical users can update subject and body by editing that email — no flow changes required. Pass subject and body directly into SendEmailV2.

To get a folder ID: in Outlook on the web, right-click the folder → open in new tab — the folder GUID is in the URL. Prefix it with Id:: in folderPath.


Teams

Teams — Post Message

"Post_Teams_Message": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
      "connectionName": "<connectionName>",
      "operationId": "PostMessageToConversation"
    },
    "parameters": {
      "poster": "Flow bot",
      "location": "Channel",
      "body/recipient": {
        "groupId": "<team-id>",
        "channelId": "<channel-id>"
      },
      "body/messageBody": "@outputs('Compose_Message')"
    }
  }
}

Variant: Group Chat (1:1 or Multi-Person)

To post to a group chat instead of a channel, use "location": "Group chat" with a thread ID as the recipient:

"Post_To_Group_Chat": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
      "connectionName": "<connectionName>",
      "operationId": "PostMessageToConversation"
    },
    "parameters": {
      "poster": "Flow bot",
      "location": "Group chat",
      "body/recipient": "19:<thread-hash>@thread.v2",
      "body/messageBody": "@outputs('Compose_Message')"
    }
  }
}

For 1:1 ("Chat with Flow bot"), use "location": "Chat with Flow bot" and set body/recipient to the user's email address.

Active-user gate: When sending notifications in a loop, check the recipient's Azure AD account is enabled before posting — avoids failed deliveries to departed staff:

"Check_User_Active": {
  "type": "OpenApiConnection",
  "inputs": {
    "host": { "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365users",
              "operationId": "UserProfile_V2" },
    "parameters": { "id": "@{item()?['Email']}" }
  }
}

Then gate: @equals(body('Check_User_Active')?['accountEnabled'], true)


Copilot Studio

Copilot Studio — Invoke Agent

When using the Copilot Studio connector, publish the agent before running the flow. Draft/test agents can exist in the studio canvas but still be unavailable or stale through the flow connector endpoint.

If a connector action fails with an unavailable-agent or endpoint-style error, publish the agent, wait briefly for propagation, then resubmit the same flow run before changing the flow definition.


Approvals

Split Approval (Create → Wait)

The standard "Start and wait for an approval" is a single blocking action. For more control (e.g., posting the approval link in Teams, or adding a timeout scope), split it into two actions: CreateAnApproval (fire-and-forget) then WaitForAnApproval (webhook pause).

"Create_Approval": {
  "type": "OpenApiConnection",
  "runAfter": {},
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_approvals",
      "connectionName": "<connectionName>",
      "operationId": "CreateAnApproval"
    },
    "parameters": {
      "approvalType": "CustomResponse/Result",
      "ApprovalCreationInput/title": "Review: @{variables('ItemTitle')}",
      "ApprovalCreationInput/assignedTo": "approver@contoso.com",
      "ApprovalCreationInput/details": "Please review and select an option.",
      "ApprovalCreationInput/responseOptions": ["Approve", "Reject", "Defer"],
      "ApprovalCreationInput/enableNotifications": true,
      "ApprovalCreationInput/enableReassignment": true
    }
  }
},
"Wait_For_Approval": {
  "type": "OpenApiConnectionWebhook",
  "runAfter": { "Create_Approval": ["Succeeded"] },
  "inputs": {
    "host": {
      "apiId": "/providers/Microsoft.PowerApps/apis/shared_approvals",
      "connectionName": "<connectionName>",
      "operationId": "WaitForAnApproval"
    },
    "parameters": {
      "approvalName": "@body('Create_Approval')?['name']"
    }
  }
}

approvalType options:

  • "Approve/Reject - First to respond" — binary, first responder wins
  • "Approve/Reject - Everyone must approve" — requires all assignees
  • "CustomResponse/Result" — define your own response buttons

After Wait_For_Approval, read the outcome:

@body('Wait_For_Approval')?['outcome']          → "Approve", "Reject", or custom
@body('Wait_For_Approval')?['responses'][0]?['responder']?['displayName']
@body('Wait_For_Approval')?['responses'][0]?['comments']

The split pattern lets you insert actions between create and wait — e.g., posting the approval link to Teams, starting a timeout scope, or logging the pending approval to a tracking list.