56 results available
Sign in
Skills directory

flowstudio-power-automate-build

github

·

Last changed
Not yet hardened

This version isn't hardened by Chainguard

No hardening report available.


SKILL.md

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 stored in the FLOWSTUDIO_MCP_TOKEN environment variable. 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


Python Helper

import json, urllib.request, os
from datetime import datetime, timezone

MCP_URL   = "https://mcp.flowstudio.app/mcp"
# Never hardcode credentials. Set the FLOWSTUDIO_MCP_TOKEN environment variable
# before running. See the flowstudio-power-automate-mcp skill for token setup.
MCP_TOKEN = os.environ["FLOWSTUDIO_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:
        # Response body is not logged as it may contain sensitive information.
        raise RuntimeError(f"MCP HTTP {e.code}: [response body redacted — check MCP_TOKEN validity and permissions]") 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.

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.


Advanced Usage

Admin Mode Enumeration


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.

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 for the full schema and these action pattern references for copy-paste templates:

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
}

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": "<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:

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 " + datetime.now(timezone.utc).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)

Common deployment errors

Error message (contains)CauseFix

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

Updated flows (have prior runs) — ANY trigger type

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.

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:

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"])
    # Extract only non-sensitive fields; inspect the Power Automate portal for full details.
    action = err["failedActions"][-1]
    action_name = action.get('name', 'unknown')
    error_code = action.get('code', 'unknown')
    error_msg = action.get('message', '')
    raise Exception(
        f"Action '{action_name}' failed with code: {error_code}. {error_msg} "
        f"Inspect the full run in the Power Automate portal for connection or input details."
    )

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

MistakeConsequencePrevention

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

Ask the user if they want to add the flow to a solution. If they confirm, obtain the target solutionId from the user. Note: add_live_flow_to_solution is not included in the default tool grant and requires explicit user instruction before use.

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:

Locationbody/recipient formatExample

Chat with Flow bot

Plain email string with trailing semicolon

"user@contoso.com;"

Channel

Object with groupId and channelId

{"groupId": "...", "channelId": "..."}


Reference Files

  • flowstudio-power-automate-mcp — Core connection setup and tool reference
  • flowstudio-power-automate-debug — Debug failing flows after deployment

Install

Need this version hardened?

Harden your own skills

Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.

Provenance

Upstream source

—

Upstream commit0ccadfda8a80f9068ad536481d8c2a8276484039
OCI reference

—

License

MIT

Files

HARDENING.md

LICENSE

SKILL.md

references/action-patterns-connectors.md

references/action-patterns-core.md

references/action-patterns-data.md

references/build-patterns.md

references/flow-schema.md

references/trigger-types.md


The trusted source for open source

Talk to an expert
PrivacyTerms

Product

Chainguard ContainersChainguard LibrariesChainguard VMsChainguard OS PackagesChainguard ActionsChainguard Agent SkillsIntegrationsPricing
© 2026 Chainguard, Inc. All Rights Reserved.
Chainguard® and the Chainguard logo are registered trademarks of Chainguard, Inc. in the United States and/or other countries.
The other respective trademarks mentioned on this page are owned by the respective companies and use of them does not imply any affiliation or endorsement.