The distinctive thing about Copilot Studio is that there is no agent code. You cannot add token logic to the agent. This is solved by two OAuth exchanges, neither of which you have to code:
- The connector performs the first OAuth on-behalf-of exchange. It does not simply forward a token — Power Platform’s Azure API Connections service takes the user’s Copilot Studio session and exchanges it for an access token audienced to your agent, using the connector’s own client credentials. You configure this on the connector’s Security tab.
- The gateway performs the second exchange, turning that token into one your MCP server accepts. You configure this on the MCP server’s Auth Data.
What you are setting up
Prerequisites
- A Copilot Studio environment, and rights to create custom connectors in the same Power Platform environment as the agent.
- An MCP server in your Azure subscription that validates Entra JWTs and speaks Streamable HTTP (Server-Sent Events is no longer supported). To write one, see Create OAuth app registration with Azure Entra.
- Entra: Cloud Application Administrator is the least-privilege role that covers creating the app registrations and granting admin consent.
- Entra registered as an identity provider in TrueFoundry. See Identity Providers.
- For the CLI path through the app registrations: Azure CLI signed in to the tenant (
az login), plusuuidgen. The portal path needs neither.
The two identities, and why there are two
“The agent’s identity” means two different objects in two different systems.
Entra decides who the agent is; TrueFoundry decides what that agent may do, and on whose behalf. The registry entry binds them by mapping a claim in the incoming Entra token to a TrueFoundry principal you can write policy against.
Copilot Studio and Entra Agent ID
Copilot Studio creates an Entra Agent ID for each agent automatically. You will find it under Entra admin center → Agents, with the person who created the agent recorded as its sponsor, and governed by a Microsoft-published agent identity blueprint.What this leads to
Because the Copilot Studio agent is a managed identity you cannot address, two objects in this setup are stand-ins for it:- Use the Agent ID for — inventory, the sponsor relationship, and Conditional Access on the agent’s own sign-ins within Microsoft 365.
- Use the TrueFoundry agent identity for the call path — which MCP servers and tools the agent may reach, and whom it may act for. That is the part the Agent ID cannot express.
If you need the agent’s Entra identity to reach the MCP server itself, the agent has to be one that can perform its own token exchange — an Azure AI Foundry or custom-hosted agent, not Copilot Studio.
Mapping to a TrueFoundry agent
Create an entry in the Agent Registry:
Embedded because Microsoft hosts the agent: the gateway is not in front of the agent, it is in front of the agent’s tools. One registry entry covers both modes below — unlike some other agent platforms, the claim the gateway matches on is the same either way.
App registrations you need
Three, and all three are needed for both modes.Entra requires that the application performing an on-behalf-of exchange is the audience of the token it redeems. The gateway therefore exchanges as
agent-service, using its client secret.Creating them
Each step below can be done in the Entra portal or with the Azure CLI — the two tabs produce the same result, so pick one and stay on it.The CLI snippets are written to run in one shell session, in order: later steps reuse
$MCP_API, $AGENT_SVC, and $AGENT_CLIENT from earlier ones. If you start a new shell, re-derive them with az ad app list --display-name <name> --query "[0].appId" -o tsv.1
mcp-api — the tool's audience
- Portal
- Azure CLI
Create the registration and set its Application ID URI to
api://<mcp-api-app-id>. Then add:- a delegated scope
Tool.Write(type: User, state Enabled) — used by on-behalf-of - an app role
Tool.InvokewithallowedMemberTypes: ["Application"]— used by client credentials
requestedAccessTokenVersion: 2 in the manifest.mcp-api needs both even if you only run one mode today.2
agent-service — the agent's audience, and the gateway's credential
- Portal
- Azure CLI
Create the registration and set its Application ID URI to
api://<agent-service-app-id> and add a delegated scope access_as_user. Then, under Expose an API → Authorized client applications, add fe053c5f-3692-4f14-aef2-ee34fc081cae, ticking access_as_user.Under API permissions, add from mcp-api:- Delegated →
Tool.Write(for on-behalf-of) - Application →
Tool.Invoke(for client credentials)
offline_access. Then create a client secret — this is the one the gateway will hold. Set requestedAccessTokenVersion: 2.Make sure Tool.Invoke is added as an Application permission, not a delegated one. Admin consent in the last step turns it into the app-role assignment on agent-service’s own service principal — which is what the client-credentials path needs. There is no separate screen for that assignment: Enterprise applications → mcp-api → Users and groups takes only users and groups, so the API-permission plus admin-consent pair above is how an application gets assigned.To verify: on
agent-service → API permissions, the Tool.Invoke row should read type Application with Status “Granted for <tenant>”. A “Not granted” warning there is the same condition that surfaces later as AADSTS501051. Granting only the delegated Tool.Write leaves it missing, because a delegated grant contributes scp to a user token and nothing at all to an app-only one.fe053c5f-… GUID is Microsoft’s Azure API Connections service — the thing that obtains tokens on your users’ behalf. Without it authorized on access_as_user, users get an interactive sign-in prompt on every call instead of a silent hand-off, which presents as a broken connector rather than a missing grant.3
agent-client — the connector's credentials
- Portal
- Azure CLI
Create the registration and add a Delegated permission on
api://<agent-service>/access_as_user, plus Microsoft Graph offline_access. Create a client secret. Under Authentication → Web → Redirect URIs, add https://global.consent.azure-apim.net/redirect.=Scope), never Application (=Role), throughout this chain. An application permission yields a token with no user to delegate — it validates cleanly and silently drops the person you were representing.4
Grant admin consent
- Portal
- Azure CLI
On both
agent-service and agent-client: API permissions → Grant admin consent.AADSTS65001 from the token endpoint.agent-service’s secret goes to TrueFoundry. agent-client’s secret goes to the Power Apps connector.The custom connector
The connector is where the agent’s token is obtained. Build it in Power Apps, not with Copilot Studio’s MCP onboarding wizard — the wizard’s OAuth options are all authorization-code and have no on-behalf-of switch, so the user’s identity does not carry through.1
Import a definition
host and the key under paths:. Keep basePath: /.2
Configure the Security tab
Resource URL is the field to get right. It is
agent-service — not your MCP server, and not the gateway. Point it at the MCP and the gateway has nothing left to exchange.One resource only in Scope: Entra derives the audience from the resource of the requested scopes and rejects a request naming two. offline_access is resource-agnostic and yields the refresh token.3
Share the connector
Can view for end users, Can edit for makers. Without this the connector works for its author and fails for everyone else, with an error that says nothing about sharing. (Sharing is not needed for yourself — the owner can always use their own connector.)
4
Add it to the agent
In Copilot Studio: Settings → Generative AI → Orchestration = Generative first, then Tools → Add a tool → Connector → your connector → Add to agent, and create a connection.With classic orchestration the agent never calls MCP tools at all — the tool appears attached and is silently ignored. And because on-behalf-of is on, creating the connection shows a short consent card rather than a full sign-in page; a full sign-in page means on-behalf-of is not active.
Choosing between client credentials and on-behalf-of
Both modes share the same connector, the same agent, and the same MCP server. Only the gateway’s outbound exchange differs — so you register the MCP server twice and point the connector at whichever entry you want.Part 1: On-behalf-of
The signed-in user’s identity is preserved all the way to your MCP server.Token flow
Register the MCP server on Truefoundry
Then add
agent:<your-agent> as an MCP Server User collaborator.
Three fields are easy to get wrong:
- Grant Type = JWT Bearer, not Token Exchange. Token Exchange is the RFC 8693 profile other identity providers use; Entra implements RFC 7523 (
jwt-bearer+requested_token_use=on_behalf_of) and rejects the other shape. - Client ID =
agent-service, matching the inbound token’s audience. Anything else givesAADSTS50013. - Scopes names the destination — your MCP server. Point it elsewhere and the exchange succeeds while your MCP server rejects the audience, which reads like an MCP bug.
What your MCP server receives
Tool.Write in scp. Use oid as the stable key for the user, not sub — in Entra sub is a pairwise identifier that differs per application, so the same person gets a different sub at every audience.
What you can enforce
Part 2: Client credentials
The gateway calls your MCP server under one shared authority. The user is authenticated at the gateway and then deliberately not propagated.Token flow
Everything up to the gateway is identical to Part 1 — same connector, same TOKEN A, same inbound validation and RBAC. Only exchange 2 changes.Register the MCP server
The same MCP server, registered a second time. Only two fields differ from Part 1:/.default, not Tool.Write. An app-only grant cannot request named delegated scopes; it resolves whatever app roles the service principal holds — which is why Tool.Invoke had to be assigned to agent-service, not merely granted.
Add the same agent as an MCP Server User here too.
What your MCP server receives
Tool.Invoke in roles.
What you can enforce
RBAC you can set using the agent identity
Once an agent resolves at the gateway it becomes a first-class principal in your access policy, alongside users, teams and virtual accounts.
Two properties are what a per-credential model cannot give you:
Access and delegation are separate grants. Being a collaborator on an MCP server lets the agent reach it. Agent Access decides whom it may act for once there. A user with no Agent Access cannot be acted for — even if that same user could reach the MCP server directly. Both must hold.
Every hop is checked against the agent actually calling. In a chain of agents, each needs its own access to the target and its own permission to act for the user. Nothing is inherited from the caller, so a chain cannot accumulate reach that no single agent was granted.
Comparison and limits
Two limits worth knowing
The Copilot Studio Entra Agent ID never reaches your MCP server — and cannot be registered directly anywhere in this chain. If you need the agent’s own Entra identity to travel with the call, the agent must be one that performs its own token exchange — Azure AI Foundry or custom-hosted, not Copilot Studio. Entra emits no nested delegation chain.azp is single-valued and is overwritten at each exchange, so the token your MCP server receives names the app that performed the last exchange — agent-service — not every hop before it. Per-hop attribution lives in the gateway’s audit trail.
Scaling to many agents
Point every agent at the sameagent-service audience. The audience identifies the tier of callers, while each agent is still distinguished by its own agent-client ID in the token. A second agent needs its own agent-client plus one Agent Registry entry; mcp-api and agent-service are shared.
Troubleshooting
Next steps
- Agent Registry — the full registration flow.
- Agent and its identity with TrueFoundry — how agent identity differs from a user or a virtual account, and how delegation is bounded.
- Agent Governance with Microsoft Entra — the wider Entra scenario.
- MCP Gateway authentication and security — every inbound and outbound option, not just these two.