Drive Copilot Managed Runtime apps from Power Automate and Copilot Studio
September 25, 2026
Microsoft Copilot Managed Runtime lets you build web apps with React, TypeScript, and Vite, then host, deploy, and share them through Power Platform. The shipped tooling is a terminal command — ms, from the @microsoft/managed-apps-cli npm package. Copilot Managed Runtime wraps the same server API so you can drive that lifecycle from a flow or an agent instead.
The connector runs in two modes off one definition: 22 typed REST operations for Power Automate and Power Apps, and a /mcp endpoint that gives a Copilot Studio agent the same surface as tools.
Finding the API
There’s no published REST reference for this service yet, so the surface here was derived from the shipped CLI and then confirmed against the live tenant. The confirmation came from contrasting two responses on an environment-scoped host:
| Request | Response |
|---|---|
GET /appframework/apps?api-version=1 |
403 — InsufficientDelegatedPermissions |
GET /appframework/totally-not-a-route?api-version=1 |
404 — RouteNotFound |
An unknown path is rejected by routing. /appframework/apps gets past routing and fails only on authorization, so the route is real and serving traffic. The 403 body names exactly what it wants:
{
"code": "Forbidden",
"innererror": {
"code": "InsufficientDelegatedPermissions",
"message": "Authorization denied: Application missing required delegated permissions: [PowerApps.Apps.Read, ManagedApps.ReadWrite.All, All.All.ReadWrite]"
}
}
ManagedApps.ReadWrite.All is a published delegated scope on the first-party Power Platform API application 8578e004-a5c6-46e7-913e-12f58912df43, and it’s user-consentable. That makes this a supported surface rather than an internal backchannel.
The host isn’t the host
Copilot Managed Runtime calls don’t go to api.powerplatform.com. They go to an environment-scoped host derived from the environment ID: lowercase it, strip the dashes, then split the last two characters off into their own label.
b5e65502-f2e4-ecc7-8e66-df670e46b100
→ b5e65502f2e4ecc78e66df670e46b1.00.environment.api.powerplatform.com
The swagger declares api.powerplatform.com to satisfy Power Platform, and script.csx rewrites every request to the real host. That’s why every operation takes an environmentId and why all of them are listed in scriptOperations. Default environments carry a Default- prefix on the ID, and the same normalization handles them.
In the designer the environmentId comes from a dynamic dropdown, so you pick an environment by name.
What you get
Every operation except Get environment dropdown takes an environmentId.
Apps
| Operation | Description |
|---|---|
ListApps |
List apps in an environment, filtered by read or write permission, with $skiptoken paging |
CreateApp |
Create an app and its backing repository |
GetApp |
Get one app by ID |
DeleteApp |
Delete an app, reporting whether it existed |
LocateApp |
Resolve the tenant, environment, and geo hosting an app |
Build and deploy
| Operation | Description |
|---|---|
BuildApp |
Start a server-side build from the backing repository |
GetBuildOperation |
Poll build status and result |
GetBuildOperationLog |
Fetch the build log for diagnosing failures |
DeployApp |
Deploy a built commit so users can reach it |
UploadBuild |
Upload a prebuilt zip; the service commits it and returns a commit SHA |
Sharing and access
| Operation | Description |
|---|---|
ListShareLinks |
List share links granting access to an app |
CreateShareLink |
Create a share link |
DeleteShareLink |
Revoke a share link |
ListRoleAssignments |
List users and groups assigned roles, with paging |
ModifyRoleAssignments |
Grant and revoke roles in one call |
Connectivity
| Operation | Description |
|---|---|
ListConnectors |
Connectors available as app data sources |
GetConnector |
Metadata for one connector |
ListConnections |
The caller’s existing connections for a connector |
Repository binding
| Operation | Description |
|---|---|
StartGitHubAuth |
Start authorization for a repository URL; returns a one-time code, verification URI, and session ID |
GetGitHubAuthStatus |
Poll the session until it reaches a terminal state |
Two ways to ship a build
The normal path is BuildApp, then poll GetBuildOperation, then DeployApp.
UploadBuild skips the server-side build. Send a zip of the built output, the service commits it and returns a commitSha, and you pass that SHA to DeployApp. It’s the one operation that writes code into the app’s repository, and it isn’t a substitute for Git — ordinary source control stays git add, git commit, git push, which is what the repository binding operations exist to authorize.
Watch the size. Power Platform caps the total payload through a custom connector at 100 MB while the service itself accepts 500 MB, so the connector ceiling is the one you hit first. The script checks before sending, so an oversized artifact fails immediately with a clear message instead of a late 413. Over MCP the effective limit is lower still, since the zip has to be base64 encoded and base64 adds roughly a third. For anything larger, run ms deploy --artifact <file>.zip.
Binding a GitHub repository
Pointing an app at your own repository requires the repository owner to authorize the Managed Apps GitHub App, which runs as a device code flow across two operations.
StartGitHubAuth takes a full repository URL such as https://github.com/owner/repo and parses out the owner, repository, and GitHub base URI. GitHub Enterprise Cloud hosts work too. Show the user the returned userCode and verificationUri, then poll GetGitHubAuthStatus no faster than pollIntervalSeconds until expiresIn elapses.
| State | Meaning |
|---|---|
Pending |
Sign-in isn’t finished. Keep polling |
Complete |
Authorized. githubLogin and mappingExpiresAt are populated |
Denied |
The user declined. Binding can’t proceed |
NotFound |
The session is unknown, usually a server restart. Start a new one |
Expired |
The window closed before sign-in finished. Start a new one |
Pass the oauthBaseUri from StartGitHubAuth straight back into GetGitHubAuthStatus. It defaults to https://github.com, which is wrong for GitHub Enterprise Cloud repositories.
The agent side
The /mcp endpoint speaks JSON-RPC 2.0 and implements initialize, tools/list, tools/call, resources/list, ping, and the notifications/* methods. Tools carry a managedapps_ prefix and mirror the REST operations, plus one extra: managedapps_list_environments, so an agent can find the environmentId every other tool needs.
A few requests an agent handles end to end:
- “Which apps can I edit in the Contoso environment?” →
managedapps_list_environments, thenmanagedapps_list_appswithpermission: "write" - “Deploy the latest build of the expense tracker.” →
managedapps_list_apps,managedapps_build_app, pollmanagedapps_get_build_operation, thenmanagedapps_deploy_app - “Why did that build fail?” →
managedapps_get_build_log - “Who has access to this app?” →
managedapps_list_role_assignmentsandmanagedapps_list_share_links - “Connect my app to github.com/contoso/expenses.” →
managedapps_start_github_auth, show the code and URI, then pollmanagedapps_get_github_auth_status
managedapps_upload_build takes the zip as base64 in zipContentBase64.
A note on naming
The product was called Managed Apps before the rename to Copilot Managed Runtime, and the old name survives in identifiers that aren’t safe to change:
| Identifier | Value |
|---|---|
| npm packages | @microsoft/managed-apps-cli, @microsoft/managed-apps |
| API route prefix | /appframework |
| User portal domain | managedapps.cloud.microsoft |
The connector’s operation IDs and MCP tool names keep the managedapps prefix for the same reason. Renaming them would break agents and flows already bound to them.
Licensing
Everyone who runs an app needs coverage, including you while you’re developing.
| Option | Behavior |
|---|---|
| Power Apps Premium plan | Covers all app operations without consuming Copilot Credits |
| Copilot Credits | Charged per app launch and per API call, at 0.1 credit per call |
Without sufficient credits a user is warned first and can keep working, then blocked at 20 app operations or five minutes of use, whichever comes first. Power Apps Premium users aren’t subject to those limits.
Deploy it
Register an app in Microsoft Entra ID and add these delegated permissions from the Power Platform API (8578e004-a5c6-46e7-913e-12f58912df43):
| Scope | Purpose |
|---|---|
ManagedApps.ReadWrite.All |
App lifecycle — required |
PowerApps.Apps.Read |
Reading app metadata |
PowerApps.Environments.Read |
Environment dropdown |
All three are user-consentable, so admin consent is optional unless your tenant requires it.
Replace [YOUR_CLIENT_ID] in apiProperties.json with your application ID. Leave [YOUR_REDIRECT_URL] alone — the platform generates a per-connector redirect URL on deploy and discards whatever you supply.
Because scriptOperations is non-empty, the script and the definition must always travel together. Omit --script-file and you get InvalidScriptDefinitionUrlWithNonNullOperations.
Create the connector in two steps. A script-enabled connector needs a function app assigned from a regional pool when it’s first created, and requesting that during create frequently fails:
pac auth create --environment <environment-id>
# Step 1 - create without the script
$p = Get-Content apiProperties.json -Raw | ConvertFrom-Json
$p.properties.PSObject.Properties.Remove('scriptOperations')
$p | ConvertTo-Json -Depth 40 | Set-Content "$env:TEMP\noscript.json" -Encoding UTF8
pac connector create `
--api-definition-file apiDefinition.swagger.json `
--api-properties-file "$env:TEMP\noscript.json"
# Step 2 - attach the script using the ID returned above
pac connector update `
--connector-id <connector-id> `
--api-definition-file apiDefinition.swagger.json `
--api-properties-file apiProperties.json `
--script-file script.csx
Updating an existing connector is a single pac connector update, since it already holds an assigned function app.
A success message means the row was written, not that the definition survived intact. Download it back and check for 22 operations, x-ms-agentic-protocol: mcp-streamable-1.0 on the /mcp path, and the environment dropdowns:
pac connector download --connector-id <connector-id> --outputDirectory ./verify
The generated redirect URL sits at properties.connectionParameters.token.oAuthSettings.redirectUrl in the downloaded file. Register it, or OAuth consent fails:
az ad app update --id <appId> --web-redirect-uris `
"https://global.consent.azure-apim.net/redirect" "<per-connector-url>"
That URL encodes both the connector name and the environment, so a connector deployed to dev and prod produces two different URLs and both need registering.
Caveats
This is public preview, and explicitly volatile. The microsoft/managed-apps README states that APIs, templates, and tooling may change before general availability, and there’s no versioned public contract for these routes. Treat the connector as tracking a moving target.
Repository create and delete are excluded. The CLI builds routes under /appframework/git/repositories, but that path returned RouteNotFound during testing while /appframework/apps did not — feature-gated or not uniformly deployed. Those operations are left out rather than shipped broken. The GitHub device code routes under /appframework/github/auth are a separate surface and are included.
Response schemas are best-effort, reconstructed from the CLI’s own mock fixtures and type usage. The script projects responses defensively, reading both flat and properties-nested shapes, so a server-side shape change degrades to null fields rather than a hard failure.
api-version=1 is pinned. Deployment has a newer manageddevops backend behind a CLI feature gate; this connector uses the stable appframework path.
When something comes back wrong
| Symptom | Cause | Fix |
|---|---|---|
pac connector create fails when --script-file is supplied |
A new script-enabled connector couldn’t be assigned a function app | Create without the script, then attach it with pac connector update |
InvalidScriptDefinitionUrlWithNonNullOperations |
scriptOperations is declared but no script was uploaded |
Add --script-file script.csx, or remove scriptOperations |
401 on every call after deploying |
The generated per-connector redirect URL isn’t registered | Read it back with pac connector download and add it |
| A connection can’t be created at all | clientId is still [YOUR_CLIENT_ID] |
Set your real app registration ID and redeploy |
403 InsufficientDelegatedPermissions |
The app registration is missing ManagedApps.ReadWrite.All |
Add the delegated permission and reconsent |
UploadBuild rejects the artifact before sending |
The zip exceeds 100 MB, or the content isn’t a zip | Use ms deploy --artifact for large artifacts |
UploadBuild returns an invalid artifact error |
The zip doesn’t contain the built output and its configuration file | Package the build output directory, not the source tree |
DeployApp reports no successful build |
No green build exists for the commit | Upload or push a commit and wait for its build to succeed |
GitHub authorization never leaves Pending |
Sign-in isn’t finished at the verification URI | Resend the code and URI; the session ends after expiresIn seconds |
Telemetry is off by default. Replace APP_INSIGHTS_CONNECTION_STRING in script.csx with your connection string to record MCP method calls, tool invocations, and tool errors. Telemetry failures are swallowed and never affect the caller.