flowstudio-power-automate-build — GitHub Copilot skill
Build, deploy, and test Power Automate cloud flows through the FlowStudio MCP server. Use it when an agent needs to create or edit a workflow without opening the portal.
npx skills add github/awesome-copilot --skill flowstudio-power-automate-build -a claude-codeWhat flowstudio-power-automate-build does
This skill guides GitHub Copilot through a Power Automate cloud-flow build: load the current tools, check for an existing flow, resolve connections, construct the definition, deploy it, then verify and test the result. It covers new flows, action updates, connection wiring, flow JSON, and patches to existing definitions.
The package includes step-by-step build patterns and a Python helper for calling MCP tools. A reachable FlowStudio MCP server with a valid JWT and a FlowStudio MCP subscription are required. Required connector connections must be active; the workflow checks them through the API and relies on OAuth when a connection has to be created.
Install it when GitHub Copilot needs to operate Power Automate through MCP. It is a poor fit for work that does not use FlowStudio MCP.
What it helps with
- create a Power Automate cloud flow from scratch
- generate flow JSON for a workflow
- update actions in an existing flow
- wire a flow to active connector connections
- deploy and test a flow definition
Example prompts for the agent
- Create a Power Automate flow for new files and deploy it through FlowStudio MCP.
- Check whether this flow already exists, then update its actions and connections.
- Build the flow JSON from scratch, resolve the required connections, and test it.
How to install flowstudio-power-automate-build
1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill flowstudio-power-automate-build -a claude-code2Manually: put the files in place
~/.claude/skills/flowstudio-power-automate-build/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/flowstudio-power-automate-build/
Manually: put the files in place
.agents/skills/flowstudio-power-automate-build/SKILL.mdFor all projects use ~/.agents/skills/
Manually: put the files in place
.cursor/skills/flowstudio-power-automate-build/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill flowstudio-power-automate-build2GitHub CLI
gh skill install github/awesome-copilot flowstudio-power-automate-build3Manually: put the files in place
.github/skills/flowstudio-power-automate-build/SKILL.mdPlatform CLI
gemini skills install https://github.com/github/awesome-copilot --path skills/flowstudio-power-automate-buildManually: put the files in place
.devin/skills/flowstudio-power-automate-build/SKILL.mdThe legacy .windsurf/skills/ path still works
Claude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Similar
Questions about flowstudio-power-automate-build
How do I install flowstudio-power-automate-build in GitHub Copilot?
Run npx skills add github/awesome-copilot --skill flowstudio-power-automate-build, then use .github/skills/flowstudio-power-automate-build/SKILL.md.
What does flowstudio-power-automate-build do?
The skill helps GitHub Copilot create and edit Power Automate cloud flows through FlowStudio MCP. It also deploys and tests flow definitions.
What is required for the skill to work?
You need a reachable FlowStudio MCP server with a valid JWT, a FlowStudio MCP subscription, and active connections for the connectors used by the flow.
Source text
SKILL.md · MIT
---
name: flowstudio-power-automate-build
description: >-
Build, scaffold, and deploy Power Automate cloud flows using the FlowStudio
MCP server. Your agent constructs flow definitions, wires connections, deploys,
and tests — all via MCP without opening the portal.
Load this skill when asked to: create a flow, build a new flow,
deploy a flow definition, scaffold a Power Automate workflow, construct a flow
JSON, update an existing flow's actions, patch a flow definition, add actions
to a flow, wire up connections, or generate a workflow definition from scratch.
Requires a FlowStudio MCP subscription — see https://mcp.flowstudio.app
---
# Build & Deploy Power Automate Flows with FlowStudio MCP
Step-by-step guide for constructing and deploying Power Automate cloud flows
programmatically through the FlowStudio MCP server.
**Prerequisite**: A FlowStudio MCP server must be reachable with a valid JWT.
See the `flowstudio-power-automate-mcp` skill for connection setup.
Subscribe at https://mcp.flowstudio.app
Workflow:
1. Load current build tools.
2. Check for an existing flow.
3. Resolve connection references.
4. Build the definition.
5. Deploy.
6. Verify.
7. Test.
---
## Source of Truth
> **Always call `list_skills` / `tool_search` first** to confirm available tool
> names and parameter schemas. Tool names and parameters may change between
> server versions.
> This skill covers response shapes, behavioral notes, and build patterns —
> things tool schemas cannot tell you. If this document disagrees with
> `tool_search` or a real API response, the API wins.
---
## Python Helper
```python
import json, urllib.request
MCP_URL = "https://mcp.flowstudio.app/mcp"
MCP_TOKEN = "<YOUR_JWT_TOKEN>"
def mcp(tool, **kwargs):
payload = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": tool, "arguments": kwargs}}).encode()
req = urllib.request.Request(MCP_URL, data=payload,
headers={"x-api-key": MCP_TOKEN, "Content-Type": "application/json",
"User-Agent": "FlowStudio-MCP/1.0"})
try:
resp = urllib.request.urlopen(req, timeout=120)
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", errors="replace")
raise RuntimeError(f"MCP HTTP {e.code}: {body[:200]}") from e
raw = json.loads(resp.read())
if "error" in raw:
raise RuntimeError(f"MCP error: {json.dumps(raw['error'])}")
return json.loads(raw["result"]["content"][0]["text"])
ENV = "<environment-id>" # e.g. Default-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```
---
## 0. Load the Current Build Tools
For a brand-new flow, load the server's `create-flow` bundle. For editing an
existing flow, load `build-flow`. This keeps the agent aligned with the MCP
server's current schema before constructing JSON.
```python
schemas = mcp("tool_search", query="skill:create-flow")
# Includes list_live_environments, list_live_connections,
# describe_live_connector, get_live_dynamic_options, update_live_flow.
```
If you need a tool outside the bundle, load it explicitly:
```python
mcp("tool_search", query="select:get_live_dynamic_properties")
```
---
## 1. Safety Check: Does the Flow Already Exist?
Always look before you build to avoid duplicates:
```python
results = mcp("list_live_flows",
environmentName=ENV,
mode="owner",
search="My New Flow",
top=20)
# list_live_flows returns { "flows": [...], "mode": "...", ... }
matches = [f for f in results["flows"]
if "My New Flow".lower() in f["displayName"].lower()]
if len(matches) > 0:
# Flow exists — modify rather than create
FLOW_ID = matches[0]["id"] # plain UUID from list_live_flows
print(f"Existing flow: {FLOW_ID}")
defn = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)
else:
print("Flow not found — building from scratch")
FLOW_ID = None
```
For very large environments, `list_live_flows` may return a continuation URL.
Pass it back as `continuationUrl` with the same `mode` to retrieve the next
batch. Use `mode="admin"` only when the user needs all environment flows and
the MCP identity has admin rights.
---
## 2. Obtain Connection References
Every connector action needs a `connectionName` that points to a key in the
flow's `connectionReferences` map. That key links to an authenticated connection
in the environment.
> **MANDATORY**: You MUST call `list_live_connections` first — do NOT ask the
> user for connection names or GUIDs. The API returns the exact values you need.
> Only prompt the user if the API confirms that required connections are missing.
### 2a — Find active connections
```python
conns = mcp("list_live_connections", environmentName=ENV)
active = [c for c in conns["connections"]
if c["statuses"][0]["status"] == "Connected"]
conn_map = {c["connectorName"]: c["id"] for c in active}
```
For a known connector, pass `search` to reduce output and get paste-ready
`connectionReferenceTemplate` and `hostTemplate` values:
```python
sp_conns = mcp("list_live_connections",
environmentName=ENV,
search="shared_sharepointonline")
```
### 2b — Determine which connectors the flow needs
Common connector API names: SharePoint `shared_sharepointonline`, Outlook
`shared_office365`, Teams `shared_teams`, Approvals `shared_approvals`,
OneDrive `shared_onedriveforbusiness`, Excel `shared_excelonlinebusiness`,
Dataverse `shared_commondataserviceforapps`, Forms `shared_microsoftforms`.
Flows that need no connectors, such as Recurrence + Compose + HTTP only, can
omit `connectionReferences`.
### 2c — If connections are missing, guide the user
```python
connectors_needed = ["shared_sharepointonline", "shared_office365"] # adjust per flow
missing = [c for c in connectors_needed if c not in conn_map]
if missing:
# STOP: connections require browser OAuth consent.
# Ask the user to create the missing connector connections in the
# selected environment, then re-run list_live_connections.
raise Exception(f"Missing active connections: {missing}")
```
### 2d — Build the connectionReferences block
```python
connection_references = {}
host_templates = {}
for connector in connectors_needed:
c = next(c for c in active if c["connectorName"] == connector)
connection_references[connector] = c.get("connectionReferenceTemplate") or {
"connectionName": c["id"], # the connection id from list_live_connections
"source": "Invoker",
"id": f"/providers/Microsoft.PowerApps/apis/{connector}"
}
host_templates[connector] = c.get("hostTemplate") or {
"connectionName": connector
}
```
In Step 3 action JSON, `inputs.host.connectionName` must be the map key such as
`shared_teams`, not the GUID. The GUID belongs only inside the
`connectionReferences[connector].connectionName` value. If an existing flow uses
the same connectors, you may also copy its `properties.connectionReferences`
from `get_live_flow`.
---
## 3. Build the Flow Definition
Construct the definition object. See [flow-schema.md](references/flow-schema.md)
for the full schema and these action pattern references for copy-paste templates:
- [action-patterns-core.md](references/action-patterns-core.md) — Variables, control flow, expressions
- [action-patterns-data.md](references/action-patterns-data.md) — Array transforms, HTTP, parsing
- [action-patterns-connectors.md](references/action-patterns-connectors.md) — SharePoint, Outlook, Teams, Approvals
```python
definition = {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"triggers": { ... }, # see trigger-types.md / build-patterns.md
"actions": { ... } # see ACTION-PATTERNS-*.md / build-patterns.md
}
```
> See [build-patterns.md](references/build-patterns.md) for complete, ready-to-use
> flow definitions covering Recurrence+SharePoint+Teams, HTTP triggers, and more.
### Discover connector operations before guessing JSON
For connector-backed triggers/actions, prefer the live connector describer over
hand-written shapes. It can return authored hints, canonical examples, variant
keys, inputs/outputs, and dynamic metadata pointers.
```python
# Search across connectors when you know the user's intent but not the API.
matches = mcp("describe_live_connector",
environmentName=ENV,
search="send email",
top=5)
# Describe a specific operation before copying an exampleDefinition.
op = mcp("describe_live_connector",
environmentName=ENV,
connectorName="shared_office365",
operationId="SendEmailV2")
print(op.get("hint"))
```
When an operation has multiple authored variants, request the variant the flow
needs:
```python
teams_chat = mcp("describe_live_connector",
environmentName=ENV,
connectorName="shared_teams",
operationId="PostMessageToConversation",
variant="flowbot_chat")
```
When the operation description says a parameter has dynamic options or dynamic
properties, call the indicated next tool:
```python
sp_op = mcp("describe_live_connector",
environmentName=ENV,
connectorName="shared_sharepointonline",
operationId="GetItems")
sites = mcp("get_live_dynamic_options",
environmentName=ENV,
connectorName="shared_sharepointonline",
connectionName=conn_map["shared_sharepointonline"],
operationId="GetItems",
parameterName="dataset",
dynamicMetadata=sp_op["dynamicParameters"]["dataset"])
fields = mcp("get_live_dynamic_properties",
environmentName=ENV,
connectorName="shared_sharepointonline",
connectionName=conn_map["shared_sharepointonline"],
operationId="GetItems",
parameterName="item",
parameters={"dataset": "<site-url>", "table": "<list-id>"},
dynamicMetadata=sp_op["dynamicProperties"]["item"])
```
Use dynamic options for dropdown IDs such as SharePoint sites/lists and Teams
teams/channels. Use dynamic properties for schema/field shapes such as
SharePoint list item columns.
---
## 4. Deploy (Create or Update)
`update_live_flow` handles both creation and updates in a single tool.
### Create a new flow (no existing flow)
Omit `flowName` — the server generates a new GUID and creates via PUT:
```python
definition["description"] = "Weekly SharePoint → Teams notification flow, built by agent"
result = mcp("update_live_flow",
environmentName=ENV,
# flowName omitted → creates a new flow
definition=definition,
connectionReferences=connection_references,
displayName="Overdue Invoice Notifications"
)
if result.get("error") is not None:
print("Create failed:", result["error"])
else:
# Capture the new flow ID for subsequent steps
FLOW_ID = result["created"]
print(f"✅ Flow created: {FLOW_ID}")
```
### Update an existing flow
Provide `flowName` to PATCH:
```python
definition["description"] = (
"Updated by agent on " + __import__('datetime').datetime.utcnow().isoformat()
)
result = mcp("update_live_flow",
environmentName=ENV,
flowName=FLOW_ID,
definition=definition,
connectionReferences=connection_references,
displayName="My Updated Flow"
)
if result.get("error") is not None:
print("Update failed:", result["error"])
else:
print("Update succeeded:", result)
```
> ⚠️ `update_live_flow` always returns an `error` key.
> `null` (Python `None`) means success — do not treat the presence of the key as failure.
>
> ⚠️ Flow description lives at `definition["description"]`. The current server
> appends `#flowstudio-mcp` for usage tracking. Do not pass a top-level
> `description` argument unless `tool_search` shows one in the active schema.
### Common deployment errors
| Error message (contains) | Cause | Fix |
|---|---|---|
| `missing from connectionReferences` | An action's `host.connectionName` references a key that doesn't exist in the `connectionReferences` map | Ensure `host.connectionName` uses the **key** from `connectionReferences` (e.g. `shared_teams`), not the raw GUID |
| `ConnectionAuthorizationFailed` / 403 | The connection GUID belongs to another user or is not authorized | Re-run Step 2a and use a connection owned by the current `x-api-key` user |
| `InvalidTemplate` / `InvalidDefinition` | Syntax error in the definition JSON | Check `runAfter` chains, expression syntax, and action type spelling |
| `ConnectionNotConfigured` | A connector action exists but the connection GUID is invalid or expired | Re-check `list_live_connections` for a fresh GUID |
---
## 5. Verify the Deployment
```python
check = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)
# Confirm state
print("State:", check["properties"]["state"]) # Should be "Started"
# If state is "Stopped", use set_live_flow_state — NOT update_live_flow
# mcp("set_live_flow_state", environmentName=ENV, flowName=FLOW_ID, state="Started")
# Confirm the action we added is there
acts = check["properties"]["definition"]["actions"]
print("Actions:", list(acts.keys()))
```
---
## 6. Test the Flow
> **MANDATORY**: Before triggering any test run, **ask the user for confirmation**.
> Running a flow has real side effects — it may send emails, post Teams messages,
> write to SharePoint, start approvals, or call external APIs. Explain what the
> flow will do and wait for explicit approval before calling `trigger_live_flow`
> or `resubmit_live_flow_run`.
### Updated flows (have prior runs) — ANY trigger type
> **Use `resubmit_live_flow_run` first.** It works for EVERY trigger type —
> Recurrence, SharePoint, connector webhooks, Button, and HTTP. It replays
> the original trigger payload. Do NOT ask the user to manually trigger the
> flow or wait for the next scheduled run.
```python
runs = mcp("get_live_flow_runs", environmentName=ENV, flowName=FLOW_ID, top=1)
if runs:
# Works for Recurrence, SharePoint, connector triggers — not just HTTP
result = mcp("resubmit_live_flow_run",
environmentName=ENV, flowName=FLOW_ID, runName=runs[0]["name"])
print(result) # {"resubmitted": true, "triggerName": "..."}
```
### HTTP, Button, and PowerApps flows — custom test payload
Only use `trigger_live_flow` when you need to send a **different** payload
than the original run. For verifying a fix, `resubmit_live_flow_run` is
better because it uses the exact data that caused the failure.
```python
defn = mcp("get_live_flow", environmentName=ENV, flowName=FLOW_ID)
triggers = defn["properties"]["definition"]["triggers"]
manual = next(iter(triggers.values()))
print("Expected body:", manual.get("inputs", {}).get("schema"))
result = mcp("trigger_live_flow",
environmentName=ENV, flowName=FLOW_ID,
body={"name": "Test", "value": 1})
print(f"Status: {result['responseStatus']}, via: {result['invocation']}")
print(result.get("warning")) # set when a required input was missing: the run still ran, with null
```
### Brand-new non-HTTP flows
A brand-new **Recurrence** flow needs no workaround: deploy it, then run it
immediately with `trigger_live_flow` and no `body` — same as the portal's
"Run flow" button. A body is refused; scheduled triggers take no inputs.
A brand-new **connector-triggered** flow (SharePoint, webhooks) has no prior
runs and cannot fire without a real source event. Deploy with a temporary
HTTP trigger, test the actions, then swap to the production trigger:
```python
production_trigger = definition["triggers"]
definition["triggers"] = {
"manual": {"type": "Request", "kind": "Http", "inputs": {"schema": {}}}
}
result = mcp("update_live_flow", environmentName=ENV,
flowName=FLOW_ID, # omit if creating new
definition=definition, connectionReferences=connection_references,
displayName="Overdue Invoice Notifications")
FLOW_ID = FLOW_ID or result["flowName"]
mcp("trigger_live_flow", environmentName=ENV, flowName=FLOW_ID,
body={"sample": "payload"})
runs = mcp("get_live_flow_runs", environmentName=ENV, flowName=FLOW_ID, top=1)
if runs[0]["status"] == "Failed":
err = mcp("get_live_flow_run_error",
environmentName=ENV, flowName=FLOW_ID, runName=runs[0]["name"])
raise Exception(err["failedActions"][-1])
definition["triggers"] = production_trigger
mcp("update_live_flow", environmentName=ENV, flowName=FLOW_ID,
definition=definition, connectionReferences=connection_references)
```
The trigger is only the entry point; testing through HTTP still exercises the
same actions. If actions use `triggerBody()` or `triggerOutputs()`, pass a
representative `body` shaped like the production trigger payload.
---
## Gotchas
| Mistake | Consequence | Prevention |
|---|---|---|
| Missing `connectionReferences` in deploy | 400 "Supply connectionReferences" | Always call `list_live_connections` first |
| `"operationOptions"` missing on Foreach | Parallel execution, race conditions on writes | Always add `"Sequential"` |
| `union(old_data, new_data)` | Old values override new (first-wins) | Use `union(new_data, old_data)` |
| `split()` on potentially-null string | `InvalidTemplate` crash | Wrap with `coalesce(field, '')` |
| Checking `result["error"]` exists | Always present; true error is `!= null` | Use `result.get("error") is not None` |
| Flow deployed but state is "Stopped" | Flow won't run on schedule | Call `set_live_flow_state` with `state: "Started"` — do **not** use `update_live_flow` for state changes |
| Teams "Chat with Flow bot" recipient as object | 400 `GraphUserDetailNotFound` | Use plain string with trailing semicolon (see below) |
| Copilot/Skills flow not in a solution | Copilot Studio may not discover it as an agent tool | After deploy, call `add_live_flow_to_solution` with the target `solutionId` |
| Button/Skills trigger used for MCP testing | Runs even when a required input is missing (null) | Pass inputs in `trigger_live_flow` `body`; on `warning`, cancel and retry with the full body |
| Connector action missing `metadata.operationMetadataId` | Designer/run-only UI can behave inconsistently | Preserve existing IDs; add stable GUIDs for new connector actions |
| Placeholder Excel `scriptId` | Dynamic validation fails at save time | Resolve the real Office Script ID before deploying |
| SharePoint `PatchItem` omits required fields | Save can fail even if the field is not changing | Echo unchanged required fields such as `item/Title` |
| Copilot Studio connector calls a draft agent | Connector invocation can fail or hit stale behavior | Publish the agent before testing/resubmitting the flow |
### Teams `PostMessageToConversation` — Recipient Formats
The `body/recipient` parameter format depends on the `location` value:
| Location | `body/recipient` format | Example |
|---|---|---|
| **Chat with Flow bot** | Plain email string with **trailing semicolon** | `"[email protected];"` |
| **Channel** | Object with `groupId` and `channelId` | `{"groupId": "...", "channelId": "..."}` |
> **Common mistake**: passing `{"to": "[email protected]"}` for "Chat with Flow bot"
> returns a 400 `GraphUserDetailNotFound` error. The API expects a plain string.
---
## Reference Files
- [flow-schema.md](references/flow-schema.md) — Full flow definition JSON schema
- [trigger-types.md](references/trigger-types.md) — Trigger type templates
- [action-patterns-core.md](references/action-patterns-core.md) — Variables, control flow, expressions
- [action-patterns-data.md](references/action-patterns-data.md) — Array transforms, HTTP, parsing
- [action-patterns-connectors.md](references/action-patterns-connectors.md) — SharePoint, Outlook, Teams, Approvals
- [build-patterns.md](references/build-patterns.md) — Complete flow definition templates (Recurrence+SP+Teams, HTTP trigger)
## Related Skills
- `flowstudio-power-automate-mcp` — Core connection setup and tool reference
- `flowstudio-power-automate-debug` — Debug failing flows after deployment
© github · MIT · github/awesome-copilot