Files
Catherine Han f3142d77c1 feat: add power-automate-mcp skill (#896)
* feat: add power-automate-mcp skill

Connect to and operate Power Automate cloud flows via a FlowStudio MCP server.
Includes tool reference, action types, connection references, and bootstrap guide.

* fix: rename skill & address review feedback

- Rename power-automate-mcp → flowstudio-power-automate-mcp
- Fix list_live_flows docs: returns wrapper object {mode, flows, totalCount, error}, not direct array
- Fix next() syntax: add parens for generator with default arg
- Add User-Agent header to tools/list discovery example
- Standardize User-Agent to FlowStudio-MCP/1.0 (required by Cloudflare)
- Add environmentName note for list_live_connections (required despite schema)
- Update list_live_flows response shape in tool-reference.md

All response shapes verified against live MCP server.
2026-03-06 15:16:11 +11:00

18 KiB

name, description
name description
flowstudio-power-automate-mcp Connect to and operate Power Automate cloud flows via a FlowStudio MCP server. Use when asked to: list flows, read a flow definition, check run history, inspect action outputs, resubmit a run, cancel a running flow, view connections, get a trigger URL, validate a definition, monitor flow health, or any task that requires talking to the Power Automate API through an MCP tool. Also use for Power Platform environment discovery and connection management. Requires a FlowStudio MCP subscription or compatible server — see https://mcp.flowstudio.app

Power Automate via FlowStudio MCP

This skill lets AI agents read, monitor, and operate Microsoft Power Automate cloud flows programmatically through a FlowStudio MCP server — no browser, no UI, no manual steps.

Requires: A FlowStudio MCP subscription (or compatible Power Automate MCP server). You will need:

  • MCP endpoint: https://mcp.flowstudio.app/mcp (same for all subscribers)
  • API key / JWT token (x-api-key header — NOT Bearer)
  • Power Platform environment name (e.g. Default-<tenant-guid>)

Source of Truth

Priority Source Covers
1 Real API response Always trust what the server actually returns
2 tools/list Tool names, parameter names, types, required flags
3 SKILL docs & reference files Response shapes, behavioral notes, workflow recipes

Start every new session with tools/list. It returns the authoritative, up-to-date schema for every tool — parameter names, types, and required flags. The SKILL docs cover what tools/list cannot tell you: response shapes, non-obvious behaviors, and end-to-end workflow patterns.

If any documentation disagrees with tools/list or a real API response, the API wins.


All examples in this skill and the companion build / debug skills use Python with urllib.request (stdlib — no pip install needed). Node.js is an equally valid choice: fetch is built-in from Node 18+, JSON handling is native, and the async/await model maps cleanly onto the request-response pattern of MCP tool calls — making it a natural fit for teams already working in a JavaScript/TypeScript stack.

Language Verdict Notes
Python Recommended Clean JSON handling, no escaping issues, all skill examples use it
Node.js (≥ 18) Recommended Native fetch + JSON.stringify/JSON.parse; async/await fits MCP call patterns well; no extra packages needed
PowerShell ⚠️ Avoid for flow operations ConvertTo-Json -Depth silently truncates nested definitions; quoting and escaping break complex payloads. Acceptable for a quick tools/list discovery call but not for building or updating flows.
cURL / Bash ⚠️ Possible but fragile Shell-escaping nested JSON is error-prone; no native JSON parser

TL;DR — use the Core MCP Helper (Python or Node.js) below. Both handle JSON-RPC framing, auth, and response parsing in a single reusable function.


What You Can Do

FlowStudio MCP has two access tiers. FlowStudio for Teams subscribers get both the fast Azure-table store (cached snapshot data + governance metadata) and full live Power Automate API access. MCP-only subscribers get the live tools — more than enough to build, debug, and operate flows.

Live Tools — Available to All MCP Subscribers

Tool What it does
list_live_flows List flows in an environment directly from the PA API (always current)
list_live_environments List all Power Platform environments visible to the service account
list_live_connections List all connections in an environment from the PA API
get_live_flow Fetch the complete flow definition (triggers, actions, parameters)
get_live_flow_http_schema Inspect the JSON body schema and response schemas of an HTTP-triggered flow
get_live_flow_trigger_url Get the current signed callback URL for an HTTP-triggered flow
trigger_live_flow POST to an HTTP-triggered flow's callback URL (AAD auth handled automatically)
update_live_flow Create a new flow or patch an existing definition in one call
add_live_flow_to_solution Migrate a non-solution flow into a solution
get_live_flow_runs List recent run history with status, start/end times, and errors
get_live_flow_run_error Get structured error details (per-action) for a failed run
get_live_flow_run_action_outputs Inspect inputs/outputs of any action (or every foreach iteration) in a run
resubmit_live_flow_run Re-run a failed or cancelled run using its original trigger payload
cancel_live_flow_run Cancel a currently running flow execution

Store Tools — FlowStudio for Teams Subscribers Only

These tools read from (and write to) the FlowStudio Azure table — a monitored snapshot of your tenant's flows enriched with governance metadata and run statistics.

Tool What it does
list_store_flows Search flows from the cache with governance flags, run failure rates, and owner metadata
get_store_flow Get full cached details for a single flow including run stats and governance fields
get_store_flow_trigger_url Get the trigger URL from the cache (instant, no PA API call)
get_store_flow_runs Cached run history for the last N days with duration and remediation hints
get_store_flow_errors Cached failed-only runs with failed action names and remediation hints
get_store_flow_summary Aggregated stats: success rate, failure count, avg/max duration
set_store_flow_state Start or stop a flow via the PA API and sync the result back to the store
update_store_flow Update governance metadata (description, tags, monitor flag, notification rules, business impact)
list_store_environments List all environments from the cache
list_store_makers List all makers (citizen developers) from the cache
get_store_maker Get a maker's flow/app counts and account status
list_store_power_apps List all Power Apps canvas apps from the cache
list_store_connections List all Power Platform connections from the cache

Which Tool Tier to Call First

Task Tool Notes
List flows list_live_flows Always current — calls PA API directly
Read a definition get_live_flow Always fetched live — not cached
Debug a failure get_live_flow_runsget_live_flow_run_error Use live run data

⚠️ list_live_flows returns a wrapper object with a flows array — access via result["flows"].

Store tools (list_store_flows, get_store_flow, etc.) are available to FlowStudio for Teams subscribers and provide cached governance metadata. Use live tools when in doubt — they work for all subscription tiers.


Step 0 — Discover Available Tools

Always start by calling tools/list to confirm the server is reachable and see exactly which tool names are available (names may vary by server version):

import json, urllib.request

TOKEN = "<YOUR_JWT_TOKEN>"
MCP   = "https://mcp.flowstudio.app/mcp"

def mcp_raw(method, params=None, cid=1):
    payload = {"jsonrpc": "2.0", "method": method, "id": cid}
    if params:
        payload["params"] = params
    req = urllib.request.Request(MCP, data=json.dumps(payload).encode(),
        headers={"x-api-key": TOKEN, "Content-Type": "application/json",
                 "User-Agent": "FlowStudio-MCP/1.0"})
    try:
        resp = urllib.request.urlopen(req, timeout=30)
    except urllib.error.HTTPError as e:
        raise RuntimeError(f"MCP HTTP {e.code} — check token and endpoint") from e
    return json.loads(resp.read())

raw = mcp_raw("tools/list")
if "error" in raw:
    print("ERROR:", raw["error"]); raise SystemExit(1)
for t in raw["result"]["tools"]:
    print(t["name"], "—", t["description"][:60])

Core MCP Helper (Python)

Use this helper throughout all subsequent operations:

import json, urllib.request

TOKEN = "<YOUR_JWT_TOKEN>"
MCP   = "https://mcp.flowstudio.app/mcp"

def mcp(tool, args, cid=1):
    payload = {"jsonrpc": "2.0", "method": "tools/call", "id": cid,
               "params": {"name": tool, "arguments": args}}
    req = urllib.request.Request(MCP, data=json.dumps(payload).encode(),
        headers={"x-api-key": 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'])}")
    text = raw["result"]["content"][0]["text"]
    return json.loads(text)

Common auth errors:

  • HTTP 401/403 → token is missing, expired, or malformed. Get a fresh JWT from mcp.flowstudio.app.
  • HTTP 400 → malformed JSON-RPC payload. Check Content-Type: application/json and body structure.
  • MCP error: {"code": -32602, ...} → wrong or missing tool arguments.

Core MCP Helper (Node.js)

Equivalent helper for Node.js 18+ (built-in fetch — no packages required):

const TOKEN = "<YOUR_JWT_TOKEN>";
const MCP   = "https://mcp.flowstudio.app/mcp";

async function mcp(tool, args, cid = 1) {
  const payload = {
    jsonrpc: "2.0",
    method: "tools/call",
    id: cid,
    params: { name: tool, arguments: args },
  };
  const res = await fetch(MCP, {
    method: "POST",
    headers: {
      "x-api-key": TOKEN,
      "Content-Type": "application/json",
      "User-Agent": "FlowStudio-MCP/1.0",
    },
    body: JSON.stringify(payload),
  });
  if (!res.ok) {
    const body = await res.text();
    throw new Error(`MCP HTTP ${res.status}: ${body.slice(0, 200)}`);
  }
  const raw = await res.json();
  if (raw.error) throw new Error(`MCP error: ${JSON.stringify(raw.error)}`);
  return JSON.parse(raw.result.content[0].text);
}

Requires Node.js 18+. For older Node, replace fetch with https.request from the stdlib or install node-fetch.


List Flows

ENV = "Default-<tenant-guid>"

result = mcp("list_live_flows", {"environmentName": ENV})
# Returns wrapper object:
# {"mode": "owner", "flows": [{"id": "0757041a-...", "displayName": "My Flow",
#   "state": "Started", "triggerType": "Request", ...}], "totalCount": 42, "error": null}
for f in result["flows"]:
    FLOW_ID = f["id"]   # plain UUID — use directly as flowName
    print(FLOW_ID, "|", f["displayName"], "|", f["state"])

Read a Flow Definition

FLOW = "<flow-uuid>"

flow = mcp("get_live_flow", {"environmentName": ENV, "flowName": FLOW})

# Display name and state
print(flow["properties"]["displayName"])
print(flow["properties"]["state"])

# List all action names
actions = flow["properties"]["definition"]["actions"]
print("Actions:", list(actions.keys()))

# Inspect one action's expression
print(actions["Compose_Filter"]["inputs"])

Check Run History

# Most recent runs (newest first)
runs = mcp("get_live_flow_runs", {"environmentName": ENV, "flowName": FLOW, "top": 5})
# Returns direct array:
# [{"name": "08584296068667933411438594643CU15",
#   "status": "Failed",
#   "startTime": "2026-02-25T06:13:38.6910688Z",
#   "endTime": "2026-02-25T06:15:24.1995008Z",
#   "triggerName": "manual",
#   "error": {"code": "ActionFailed", "message": "An action failed..."}},
#  {"name": "08584296028664130474944675379CU26",
#   "status": "Succeeded", "error": null, ...}]

for r in runs:
    print(r["name"], r["status"])

# Get the name of the first failed run
run_id = next((r["name"] for r in runs if r["status"] == "Failed"), None)

Inspect an Action's Output

run_id = runs[0]["name"]

out = mcp("get_live_flow_run_action_outputs", {
    "environmentName": ENV,
    "flowName": FLOW,
    "runName": run_id,
    "actionName": "Get_Customer_Record"   # exact action name from the definition
})
print(json.dumps(out, indent=2))

Get a Run's Error

err = mcp("get_live_flow_run_error", {
    "environmentName": ENV,
    "flowName": FLOW,
    "runName": run_id
})
# Returns:
# {"runName": "08584296068...",
#  "failedActions": [
#    {"actionName": "HTTP_find_AD_User_by_Name", "status": "Failed",
#     "code": "NotSpecified", "startTime": "...", "endTime": "..."},
#    {"actionName": "Scope_prepare_workers", "status": "Failed",
#     "error": {"code": "ActionFailed", "message": "An action failed..."}}
#  ],
#  "allActions": [
#    {"actionName": "Apply_to_each", "status": "Skipped"},
#    {"actionName": "Compose_WeekEnd", "status": "Succeeded"},
#    ...
#  ]}

# The ROOT cause is usually the deepest entry in failedActions:
root = err["failedActions"][-1]
print(f"Root failure: {root['actionName']}{root['code']}")

Resubmit a Run

result = mcp("resubmit_live_flow_run", {
    "environmentName": ENV,
    "flowName": FLOW,
    "runName": run_id
})
print(result)   # {"resubmitted": true, "triggerName": "..."}

Cancel a Running Run

mcp("cancel_live_flow_run", {
    "environmentName": ENV,
    "flowName": FLOW,
    "runName": run_id
})

⚠️ Do NOT cancel a run that shows Running because it is waiting for an adaptive card response. That status is normal — the flow is paused waiting for a human to respond in Teams. Cancelling it will discard the pending card.


Full Round-Trip Example — Debug and Fix a Failing Flow

# ── 1. Find the flow ─────────────────────────────────────────────────────
result = mcp("list_live_flows", {"environmentName": ENV})
target = next(f for f in result["flows"] if "My Flow Name" in f["displayName"])
FLOW_ID = target["id"]

# ── 2. Get the most recent failed run ────────────────────────────────────
runs = mcp("get_live_flow_runs", {"environmentName": ENV, "flowName": FLOW_ID, "top": 5})
# [{"name": "08584296068...", "status": "Failed", ...}, ...]
RUN_ID = next(r["name"] for r in runs if r["status"] == "Failed")

# ── 3. Get per-action failure breakdown ──────────────────────────────────
err = mcp("get_live_flow_run_error", {"environmentName": ENV, "flowName": FLOW_ID, "runName": RUN_ID})
# {"failedActions": [{"actionName": "HTTP_find_AD_User_by_Name", "code": "NotSpecified",...}], ...}
root_action = err["failedActions"][-1]["actionName"]
print(f"Root failure: {root_action}")

# ── 4. Read the definition and inspect the failing action's expression ───
defn = mcp("get_live_flow", {"environmentName": ENV, "flowName": FLOW_ID})
acts = defn["properties"]["definition"]["actions"]
print("Failing action inputs:", acts[root_action]["inputs"])

# ── 5. Inspect the prior action's output to find the null ────────────────
out = mcp("get_live_flow_run_action_outputs", {
    "environmentName": ENV, "flowName": FLOW_ID,
    "runName": RUN_ID, "actionName": "Compose_Names"
})
nulls = [x for x in out.get("body", []) if x.get("Name") is None]
print(f"{len(nulls)} records with null Name")

# ── 6. Apply the fix ─────────────────────────────────────────────────────
acts[root_action]["inputs"]["parameters"]["searchName"] = \
    "@coalesce(item()?['Name'], '')"

conn_refs = defn["properties"]["connectionReferences"]
result = mcp("update_live_flow", {
    "environmentName": ENV, "flowName": FLOW_ID,
    "definition": defn["properties"]["definition"],
    "connectionReferences": conn_refs
})
assert result.get("error") is None, f"Deploy failed: {result['error']}"
# ⚠️ error key is always present — only fail if it is NOT None

# ── 7. Resubmit and verify ───────────────────────────────────────────────
mcp("resubmit_live_flow_run", {"environmentName": ENV, "flowName": FLOW_ID, "runName": RUN_ID})

import time; time.sleep(30)
new_runs = mcp("get_live_flow_runs", {"environmentName": ENV, "flowName": FLOW_ID, "top": 1})
print(new_runs[0]["status"])   # Succeeded = done

Auth & Connection Notes

Field Value
Auth header x-api-key: <JWT>not Authorization: Bearer
Token format Plain JWT — do not strip, alter, or prefix it
Timeout Use ≥ 120 s for get_live_flow_run_action_outputs (large outputs)
Environment name Default-<tenant-guid> (find it via list_live_environments or list_live_flows response)

Reference Files


More Capabilities

For diagnosing failing flows end-to-end → load the power-automate-debug skill.

For building and deploying new flows → load the power-automate-build skill.