This article details a proof-of-concept demonstrating how to integrate live ControlUp environment data into Microsoft Copilot Studio, enabling natural language queries against real-time operational telemetry.
Proof of concept. This isn’t a supported ControlUp product feature or an official integration guide — it’s a “look what you can wire together in an afternoon” walkthrough. Everything below was built and tested live in a lab tenant, including the failures. Treat it accordingly.
Here’s the idea: Microsoft Copilot Studio speaks MCP (Model Context Protocol). ControlUp ships a public MCP server (@controlup-ai/mcp) that exposes real environment data — devices, sessions, DEX scores — as MCP tools. Put a small bridge between them and you can sit in Copilot Studio and ask, in plain English:
“What’s the slowest machine in my environment?”
…and get an answer backed by live ControlUp data!

In this post
ControlUp’s MCP server is a local, stdio-based process — it’s built to be launched by a desktop client like Claude Desktop. Copilot Studio, on the other hand, wants an HTTPS endpoint speaking MCP’s Streamable HTTP transport. So the core of this POC is a tiny bridge: a container that runs the ControlUp MCP server and fronts it with supergateway, which converts stdio ⇄ Streamable HTTP.
Copilot Studio agent │ uses one application (service principal) connection ▼ Power Platform custom connector │ presents an app-only Entra token ▼ Azure Container App ── "Easy Auth" validates the token, else 401 │ ▼ supergateway (stdio → Streamable HTTP) ▼ ControlUp MCP server (@controlup-ai/mcp) ▼ ControlUp APIs
There are two security layers, and keeping them straight makes every error message later make sense:
One Entra app registration plays both roles: it’s the API being protected and the client calling it.
deploy.sh helper (download them here)Create a dedicated user, this will allow you to set a minimum role that covers the data you want the agent to read, log in as that user, and generate an API key. Record the API key and your Org ID — the bridge reads them as the API_KEY and ORG_ID environment variables.
Register a single app in Microsoft Entra ID (I called mine ControlUp MCP Bridge).

From the Overview page, record the Application (client) ID and Directory (tenant) ID — you’ll paste them into three different places later.

Then four settings, each of which has a specific job:
api://<client-id>. This is the identifier the token audience is validated against.
requestedAccessTokenVersion to 2. Easy Auth’s issuer validation expects v2 tokens; v1 tokens fail with confusing 401s.
MCP.Invoke, member type Applications). This is what makes an app-only token meaningful.

MCP.Invoke → then click Grant admin consent. The green checkmark matters; without consent, nothing downstream works.
Finally, create a client secret and copy the value immediately (it’s shown once).

Everything Azure-side is two files run from Azure Cloud Shell (Bash): a Bicep template (Container App + managed identity + Key Vault references + Easy Auth) and a deploy.sh that builds the container image in the cloud with az acr build — no local Docker anywhere.
Upload both via Cloud Shell’s Manage files → Upload:


Edit the CONFIG block at the top of deploy.sh (this is the entire configuration surface):
# ============================ CONFIG — EDIT THESE ============================
RG="rg-cu-mcp" # resource group (created if absent)
LOCATION="canadacentral" # Azure region
NAME_PREFIX="cu-mcp" # -> <prefix>-law/-env/-app/-id
ACR_NAME="" # 5-50 lowercase alphanumerics, globally unique
IMAGE_TAG="1.0.3" # image tag (match the pinned package version)
KV_NAME="" # globally unique key vault name
APP_CLIENT_ID="" # Entra app registration client ID (Step 2)
CONTROLUP_API_KEY="" # from the ControlUp service user (Step 1)
CONTROLUP_ORG_ID="" # ControlUp org ID (Step 1)
# Pinned package versions baked into the image:
CU_MCP_VERSION="1.0.3" # confirm latest: npm view @controlup-ai/mcp version
SUPERGATEWAY_VERSION="3.4.3"
# ============================================================================

Then bash deploy.sh. The script authors the Dockerfile inline, builds and pushes the image, creates the managed identity, grants it registry-pull and Key Vault access before deploying (ordering matters — Container Apps validate image pulls and secret resolution at provision time), stores your ControlUp credentials in Key Vault, and deploys the Bicep. A few minutes later:

Your MCP endpoint is https://<your-app>.<region>.azurecontainerapps.io/mcp. And here’s the first counterintuitive success signal of this project:
$ curl -i https://<your-app>...azurecontainerapps.io/mcp
HTTP/1.1 401 Unauthorized
In Power Apps → Custom connectors → New → Import an OpenAPI file, import a small connector definition whose one important operation is a POST /mcp tagged with x-ms-agentic-protocol: mcp-streamable-1.0 — that tag is what makes Copilot Studio recognize the connector as an MCP tool source.

On the Security tab: OAuth 2.0 → identity provider Azure Active Directory → and check Enable Service Principal support. Fill in the client ID, client secret, tenant, and — the field that bites people — Resource URL = api://<client-id> with Scope api://<client-id>/.default. Then Create connector.

On the connector’s 5. Test tab, click + New connection and — this is important — choose Service Principal Connection, not the interactive sign-in. Enter the client ID, secret, and tenant. No login popup should appear; that’s the tell you’re on the right flow.



Now verify auth end-to-end with the second counterintuitive success signal. Select the connection, pick the GetInvokeMCP operation (a deliberate GET), and run Test operation:

x-ms-apihub-obo: false — the request ran as the application, not on-behalf-of a user.) A 401 here means the token was rejected: recheck the Resource URL, tenant, and client ID on the Security tab.In your agent: Tools → Add a tool.

In the Add tool dialog, click the Model Context Protocol filter chip in the bottom row and select your connector-backed tool (mine shows as ControlUp MCP endpoint (Streamable HTTP)).

On the card that appears, confirm the Connection shows your service-principal connection with a green check, then Add and configure:

Now the single most important setting in this entire walkthrough. On the tool’s configuration page, expand Additional details and set Credentials to use = Maker-provided credentials (and Ask the end user before running = No). This is what makes the agent use the application connection for everyone. Leave it on the default (End-user credentials) and every user gets a “Connect to continue” interruption on first use — and scheduled/autonomous runs fail outright because there’s no live user to click it.

Save, and the ControlUp tools import straight off the server:

DOMAINS environment variable (e.g. DOMAINS=cu4d) in the deployment — a smaller tool list is also noticeably more reliable at discovery time.Open the Test pane, start a new test session, and ask a question only your environment can answer — “What’s the slowest machine in my environment?”. The agent picks the right ControlUp tool, calls it through the bridge, and reasons over the results (see the screenshot at the top of this post). That’s a Copilot Studio agent answering from live ControlUp DEX data!
DOMAINS keeps you under the 70-tool cap, improves discovery reliability, and limits what the agent can even attempt.Total moving parts: one Entra app, one container, one connector, one connection, one Copilot Studio tool setting. The result is a natural-language front end to live ControlUp telemetry inside the Microsoft ecosystem.