Skip to content

How I publish SharePoint pages from Copilot Studio: a Power Automate dispatcher pattern

How do I document my Power Automate flows? Same forum question as Posts 1 and 2, third answer.

This is the third version of the same documentation pattern. The output is now a real SharePoint Site Page per flow, plus a row in FlowDocsInventory. The agent still reads the flow definitions through Flow Studio MCP, but it publishes pages through a Power Automate dispatcher flow instead of writing SharePoint pages directly.

Post 1 used an IDE and a service principal. Post 2 stayed in Copilot Studio but produced Word docs in a document library, because Work IQ SharePoint can't write Site Pages. This post fills that gap. The agent does not get open-ended SharePoint write access; it gets one approved flow with eight named operations.

The meta detail: I had the agent build the dispatcher flow itself, using Flow Studio MCP. Same agent, same MCP server, used in the other direction. Reading flows on the way in, writing a flow on the way out.

What it took:

  • One Power Automate flow with eight cases routed by an action enum input
  • Two tools wired into the Copilot Studio agent: the dispatcher flow (for Site Pages writes) and Work IQ SharePoint (for the FlowDocsInventory list updates, same as Post 2)
  • One tool description rich enough that the agent picks the right action without per-input prompting

The agent built the flow itself with Flow Studio MCP. I added the tool description, fixed one Workflow Definition Language expression bug along the way, and pointed the agent at SharePoint.

The architecture in one line:

Agent
Copilot Studio
Read
Flow Studio MCP
flow definitions
Write
Dispatcher flow
Site Pages
Write
Work IQ
inventory rows

One agent, three tools, two write surfaces. Each Site Page write goes through the dispatcher flow; each inventory row goes through Work IQ.

Copilot Studio agent's Tools panel showing both Flow Studio Dev MCP and Work IQ SharePoint (Preview) initialized as tools, followed by the read-side tool-call sequence: list_live_environments and findSite, list_live_flows and listLists, get_live_flow x3, listColumns. Two MCP servers, one agent.
The Copilot Studio agent uses Flow Studio MCP to read flow definitions, the dispatcher flow to publish Site Pages, and Work IQ SharePoint to update the inventory list.

The dispatcher flow the agent built

One flow, eight actions, one action string input that routes to the right SharePoint Pages REST endpoint. Five optional inputs (pageId, pageName, pageTitle, pageHtml, pageContent) cover every parameter shape the endpoints need. Note that pageContent is a special case: it carries the verbatim description "INTERNAL FIELD - DO NOT ASK USER. Always pass empty string." in the trigger schema. More on why in the per-input configuration section. Flow GUID b691d18e-7d70-4e32-95c8-ccd2e01850e7, display name MCP Demo Agent Flow - Site Pages v2, built by Claude through Flow Studio MCP and added to a solution so Copilot Studio could find it.

In the designer, this is the "When an agent calls the flow" trigger. In the flow definition it appears as Request with kind: Skills. That's what makes the flow available as an agent-flow tool in Copilot Studio; a generic HTTP request trigger will not show up the same way.

The eight actions and the REST endpoints behind them:

Action Purpose Method Endpoint
ListFind existing pages on the site (pre-flight before Create or Modify)GET_api/sitepages/pages?$select=Id,Title,FileName,PageLayoutType,Modified&$orderby=Modified desc&$top=100
ReadFetch one page's full record (metadata + canvas content)GET_api/sitepages/pages({pageId})
CreateMake a new page from your HTML (body example below)POST_api/sitepages/pages
CheckoutLock the page so edits commit cleanlyPOST_api/sitepages/pages({pageId})/checkoutPage
ModifyPatch title and/or canvas. Smart-merge: only fields you pass get updated (body example below)PATCH
extra header: IF-MATCH: *
_api/sitepages/pages({pageId})
PublishPromote the checked-in version to what readers seePOST_api/sitepages/pages({pageId})/publish
DiscardAbort an in-flight edit, revert to last publishedPOST_api/sitepages/pages({pageId})/discardPage
RecycleSoft-delete the page (recoverable from recycle bin)POST_api/web/getfilebyserverrelativeurl('/sites/MCPDemo/SitePages/{pageName}.aspx')/recycle

Request bodies for Create and Modify (the only two actions that send a body):

▸ Create body
{
  "Title": "MCPDemo Upload Approval",
  "FileName": "flowdocs-upload.aspx",
  "PageLayoutType": "Article",
  "CanvasContent1": [
    {
      "controlType": 4,
      "position": {
        "layoutIndex": 1, "zoneIndex": 1,
        "sectionIndex": 1, "sectionFactor": 12,
        "controlIndex": 1
      },
      "addedFromPersistedData": true,
      "editorType": "CKEditor",
      "id": "<guid>",
      "innerHTML": "<p>Page HTML</p>"
    },
    {
      "controlType": 0,
      "pageSettingsSlice": {
        "isDefaultDescription": true,
        "isDefaultThumbnail": true
      }
    }
  ]
}
▸ Modify body (smart-merge: pass only what you want to change)
{
  "Title": "Updated title",
  "CanvasContent1": [
    {
      "controlType": 4,
      "position": { "...": "..." },
      "editorType": "CKEditor",
      "id": "<guid>",
      "innerHTML": "<p>Updated HTML</p>"
    },
    {
      "controlType": 0,
      "pageSettingsSlice": { "...": "..." }
    }
  ]
}

Default headers: every endpoint sends Accept: application/json;odata=nometadata. POST and PATCH also send Content-Type: application/json;odata=nometadata.

Modify needs IF-MATCH: * so it overwrites whatever's currently checked in, regardless of etag. Without that header you'd hit 412 Precondition Failed on every Modify call.

Recycle is the outlier: the _api/sitepages/pages REST family doesn't expose a recycle verb, so the dispatcher falls back to the generic file API by server-relative URL. That's why Recycle takes pageName instead of pageId.

CanvasContent1 on the wire: shown above as a JSON array for readability. SharePoint actually expects it as a JSON-encoded string; the SharePoint connector in Power Automate handles that stringification for you.

One detail worth surfacing: the first Build_Canvas_Create action failed deploy with InvalidTemplate. The agent traced the cause itself:

Found the root cause: my expressions use createObject(...) which isn't a valid Power Automate function. (It's Power Fx, not Workflow Definition Language.) That's why Build_Canvas_Create failed with InvalidTemplate. Need to rewrite using setProperty(json('{}'), key, value) chains instead.

▸ Body expression on Create after the WDL fix
@setProperty(setProperty(setProperty(setProperty(json('{}'),
  'Title', coalesce(triggerBody()?['pageTitle'], '')),
  'FileName', if(endsWith(...), ..., concat(..., '.aspx'))),
  'PageLayoutType', 'Article'),
  'CanvasContent1', outputs('Build_Canvas_Create'))

That's the actual Body expression on Create after the rewrite: four nested setProperty calls building the JSON SharePoint expects. Useful reminder that Power Fx and WDL look similar and aren't interchangeable inside flow expressions. The flow also auto-appends .aspx on pageName for Create and Recycle, and Modify is smart-merge: it only patches the fields the agent provides, so partial updates work without round-tripping the whole canvas.

One flow, one tool, eight endpoints behind a single action enum: that's the entire dispatcher.


The dispatcher flow in the Power Automate designer: one Agent trigger, one Switch on Action, eight cases, one case per SharePoint REST operation.

The natural lifecycle the agent chains is:

Create
Checkout
Modify
Publish

Happy path. Discard branches off Modify when the agent needs to abort an in-flight edit. List + Read are the pre-flight reads. Recycle is soft-delete, separate from the edit cycle.

What the agent did

Once the flow was deployed and added to a solution, I wired it into a Copilot Studio agent and gave it one prompt covering all three flows:

The user prompt sent to the Copilot Studio agent: Document the flows in the Flow Studio Demo environment. For each flow, create a page with these 7 sections in order: (1) What this flow is for, (2) How it is triggered, (3) Other inputs, (4) The process in plain language, (5) Relevant tools and systems, (6) A flow chart (mermaid to SVG), (7) Appendix of action inputs and parameters. Save page to https://johnliu365.sharepoint.com/sites/MCPDemo. Then add a row to the FlowDocsInventory list with Title, TriggerType, FlowURL, PageURL, RelevantTools, and Summary. Start with the three flows we picked for the page automation pilot: SQL Server 2025 GDR Monitor, MCPDemo #11 Upload Approval Process with Validation, and Weekly Pending Approvals Summary.
The single prompt I sent to the agent in the Copilot Studio test pane.

The agent worked through it in passes, every tool call showing up in the reasoning sidebar.

The Copilot Studio agent's reasoning sidebar showing the full tool-call sequence: get_live_flow read each of the three flow definitions, then seven calls to MCP Demo Agent Flow - Site Pages v2 (the dispatcher) for the Create and Publish lifecycle on each page, then three createListItem calls via Work IQ SharePoint for the FlowDocsInventory rows.
Read the flow definitions, dispatch the Site Pages writes, write the inventory rows. Two tools, one prompt.

Three pages landed on MCPDemo with matching rows in FlowDocsInventory. The agent's final summary card:

The agent's final summary message titled All 3 Documentation Pages Created and Published. A table lists the three flows (SQL Server 2025 GDR Monitor, MCPDemo #11 Upload Approval Process, Weekly Pending Approvals Summary) with their SharePoint Page IDs (11, 12, 13), View page links, and matching FlowDocsInventory item numbers (#7, #8, #9). Below the table, a 'What was delivered per flow page' section recaps the seven sections, and a 'Root cause of the earlier failure' note explains that the Skills trigger rejects null for type:string fields, fixed by passing explicit empty strings for unused parameters.
Verified end-to-end: dispatcher published all three Site Pages, Work IQ wrote all three inventory rows, the agent confirmed every artifact in one message.

One real-world detail to flag: my MCPDemo Site Pages library has page moderation enabled, so all three of these landed in Pending approval, not Published. Calling Publish through SharePoint REST does not bypass moderation. If your library has approval enabled, the agent's Publish call will succeed but the page won't be live until someone hits 'Approve' in the SharePoint UI. Worth knowing before you wire this into a customer-facing site.

Wiring it into Copilot Studio

Adding the flow to a solution is one MCP call: add_live_flow_to_solution. The flow has to live in the same environment as the Copilot Studio agent (mine are both in Default-26e65220-...); Copilot Studio's tool picker won't reach across environment boundaries.

Heads-up: Copilot Studio's tool wizard auto-adds only the required schema property (the action input). The 5 optional inputs (pageId, pageName, pageTitle, pageHtml, pageContent) sit in a "+ Add input" dropdown until you click each one explicitly. Skip this and the agent literally cannot pass those fields. This is the trap most people hit first.

The setup is three parts. All three are required, not alternatives.

Part 1 . Schema and tool description (deploy-time)

The trigger schema and the tool description give the agent's planner enough context to pick the right action and fill the right inputs without prompting the user.

  • The action input is an enum with the 8 keywords. Required.
  • The 5 optional inputs each get a description that helps the planner decide when to fill them.
  • pageContent gets the verbatim hint "INTERNAL FIELD - DO NOT ASK USER. Always pass empty string." baked into its trigger-schema description. That single line is what stops slot-filling from ever prompting the user for it.
  • The tool description (a separate paragraph the agent reads at planning time) explains routing per action.

The description I use looks roughly like this:

▸ Tool description (Copilot Studio reads this)

SharePoint Pages dispatcher for the MCPDemo site. Use this tool to List, Read, Create, Modify, Checkout, Publish, Discard, or Recycle modern Site Pages. The action input is required and must be one of those eight values.

For Create, pass pageName (short page name, .aspx is added automatically), pageTitle, and pageHtml. Do not pass pageContent; the flow handles that internally.

For Read / Modify / Checkout / Publish / Discard, pass pageId. For Recycle, pass pageName. Modify is a smart-merge: only the fields you pass are patched.

That description plus the action enum is enough for the agent to plan correctly. It is not, on its own, enough for runtime to behave. Layer 2 is what makes the runtime actually quiet.

Part 2 . Per-input config in Copilot Studio's tool wizard

This is the part the docs don't make obvious. After the schema and description are right, the agent will still misbehave (asking the user for inputs the planner could fill, or stuffing the whole prompt into one slot) until each input is configured manually in the tool wizard. The schema's required array is purely informational here; the per-input toggles are what actually controls runtime.

Verbatim from my own build notes, with the most-skipped step in bold:

It is a bit cumbersome to configure Page ID, Name, Title, HTML, Content to be left blank. The trick: Action - make 'Identified as' a custom entity as a closed list with the actions we allow. The rest - in Advanced, make How many reprompts => Don't repeat and Action if no entity found => Set variable to empty (no value). And very importantly, set Should prompt user to ticked, save, and set to unticked and save again - this is very important.

Broken down:

For the action input:

  • Identify as: create a Closed List entity called PageAction with the 8 keywords as items, bind here. Do not leave it as "User's entire response". That stuffs the whole prompt into the action slot and the schema's enum rejects with TriggerInputSchemaMismatch.

For each of the 5 optional inputs (pageId, pageName, pageTitle, pageHtml, pageContent):

  1. Click "+ Add input" to add it to the tool config (only action auto-adds).
  2. Required toggle: OFF. The schema's required array gets rewritten to all-properties by Copilot Studio's tool sync regardless, so this is the only place optionality actually sticks.
  3. Identify as: never "User's entire response". Pick "Custom" or any built-in entity that won't match.
  4. Should prompt user (the save-cycle, CRITICAL): tick Should prompt user → click Save → untick Should prompt user → click Save again. Both saves are required. Without the double-save, the unticked state does not persist and Copilot Studio keeps prompting the user for inputs the agent should fill silently. In my tenant, this save-cycle was the step that made it stick. I have not seen it documented officially; it is what worked. This is the step most people skip; it is also the step most likely to make you think your config is broken.
  5. How many reprompts: 0 / "Don't ask".
  6. Action if no entity found: Skip / Continue / None (never "Escalate").

Part 3 . One line in the agent's system prompt

Even with Layers 1 and 2 done correctly, the agent will sometimes defer back to the user when asked to "compose" or "come up with" page content. To stop that, append one line to the agent's instructions (Overview → Instructions):

When the user asks you to compose, write, draft, or "come up with" page content, you MUST author the HTML yourself. Never ask the user to provide HTML.

Schema and tool description, plus per-input config including the save-cycle, plus the system-prompt nudge. All three. The save-cycle is the part most people skip and it's the most common reason a Skills-flow tool keeps prompting the user when the agent should be filling everything silently.

If you take one thing from this post

Agent calls a flow. Flow does the writes. The dispatcher pattern lets a Copilot Studio agent reach SharePoint Site Pages through a narrow, approved set of operations: List, Read, Create, Modify, Checkout, Publish, Discard, and Recycle. The agent does not get an open-ended SharePoint REST tool. It gets one controlled flow.

Security note. The dispatcher flow is a write surface. Limit who can use the agent, keep the action enum narrow, validate the target site and page name inside the flow, and avoid accepting arbitrary REST paths from the agent. The point is not to let the agent call any SharePoint API. The point is to expose only the eight operations you approve.

Authentication context. SharePoint writes run through the flow's connection configured for the dispatcher. Check whether your agent uses end-user or maker-provided credentials before sharing it broadly. That determines whose SharePoint permissions actually authorize the writes.

Try it in your tenant

  1. Build the dispatcher flow. Trigger: Request with kind: Skills (the Copilot Studio Skills trigger). Body: a Switch on the action input, fanning out to eight HTTP-with-Microsoft-Entra-ID calls. Seven hit _api/sitepages/pages endpoints; Recycle hits _api/web/getfilebyserverrelativeurl(...)/recycle.
  2. Add the flow to a solution. Ask the agent to call add_live_flow_to_solution, or do it manually through Solutions in the maker portal.
  3. Add two tools to the agent. Wire the dispatcher flow as a Copilot Studio tool taking Path A (drop the optional inputs and paste a tool description like the one above). Also add Work IQ SharePoint as a second tool, scoped to your inventory list (same as Post 2).
  4. Test with a real prompt in the test pane. "Create a page called Release-Notes-Apr with content <h2>What shipped</h2>..." is a good first call. Watch it land, then refresh Site Pages.
  5. Watch the limits. Copilot Studio agent-flow tools have a 100-second action limit, so the dispatcher should perform one page operation per call and return quickly. Each agent-flow action also consumes Copilot Studio capacity, so batch page generation should start with a small set before scaling.

What I'd love to hear

If you build this and watch the runs, I'd love to hear which action the agent reaches for first on its own. There's a real chance the system prompt and the tool description both nudge it toward List as a safety check before mutating anything. Also worth comparing notes on: how much of the per-input config you really needed once the tool description was good enough.


About Flow Studio MCP: Flow Studio MCP is a Model Context Protocol server that gives AI agents action-level visibility into Power Automate, including the ability to deploy flows and add them to solutions. It's listed on GitHub's awesome-copilot. Works with Microsoft Copilot Studio, GitHub Copilot, Claude, and any MCP-compatible agent.

Related reading:


Catherine Han, Flow Studio