Install
$ agentstack add skill-ninihen1-power-automate-mcp-skills-flowstudio-power-automate-build ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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:
- Load current build tools.
- Check for an existing flow.
- Resolve connection references.
- Build the definition.
- Deploy.
- Verify.
- 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
import json, urllib.request
MCP_URL = "https://mcp.flowstudio.app/mcp"
MCP_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 = "" # 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.
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:
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:
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
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:
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
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
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
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.
# 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:
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:
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": "", "table": ""},
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:
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:
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
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.
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-triggered 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.
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']}")
Brand-new non-HTTP flows (Recurrence, connector triggers, etc.)
A brand-new Recurrence or connector-triggered flow has no prior runs to resubmit and no HTTP endpoint to call. This is the ONLY scenario where you need the temporary HTTP trigger approach below. Deploy with a temporary HTTP trigger first, test the actions, then swap to the production trigger.
Compact recipe:
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["created"]
test = 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 trigger_live_flow.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 | Alway
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ninihen1
- Source: ninihen1/power-automate-mcp-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.