Entra ID On-Behalf-Of MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Entra ID On-Behalf-Of MCPverify my identity and list my recent emails"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Entra ID On-Behalf-Of MCP (FastMCP)
Example Model Context Protocol server that shows how Microsoft Entra ID On-Behalf-Of (OBO) works.
The MCP client authenticates to this server. The server does not forward that token to Microsoft Graph. It uses MSAL acquire_token_on_behalf_of to mint a new token whose audience is Graph, then calls Graph as the signed-in user.
It also runs locally and on Amazon Bedrock AgentCore Runtime, deployable with AWS CDK.
User
|
| device code / MCP OAuth
v
Entra ID -- access token A (aud = MCP app, scp = access_as_user)
|
v
MCP client (Claude, VS Code, client_demo.py)
| Authorization: Bearer A
v
FastMCP server (local or AgentCore Runtime)
| MSAL acquire_token_on_behalf_of
| POST /oauth2/v2.0/token
| grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
| requested_token_use=on_behalf_of
| assertion=A
| scope=https://graph.microsoft.com/User.Read
v
Entra ID -- access token B (aud = Graph, same user oid, azp = MCP app)
|
v
Microsoft Graph /me /me/messagesEntra OBO is not RFC 8693 Token Exchange. It is RFC 7523 jwt-bearer plus requested_token_use=on_behalf_of.
Tools
Tool | What it proves |
| Inbound JWT |
| OBO minted a different token; Graph |
| Second OBO with |
| Protocol, endpoint, and the rules that cause AADSTS50013 / 65001 |
Related MCP server: MCP Credentials Broker
1. Entra app registration
One confidential client is enough for this example. It plays two roles: resource (MCP audience) and client (the app that performs OBO).
Entra admin center → App registrations → New registration
Name:
entra-id-obo-mcpAccounts in this organizational directory only
Certificates & secrets → New client secret. Copy the value.
Expose an API
Application ID URI: accept
api://<application-client-id>Add scope
Name:
access_as_userWho can consent: Admins and users
Admin consent display name / description: allow the MCP server to take actions as the signed-in user
API permissions → Add a permission → Microsoft Graph → Delegated
User.ReadMail.ReadGrant admin consent for the tenant (required if the MCP client cannot request those Graph scopes at login)
Authentication
Platform: Mobile and desktop applications
Enable Allow public client flows (needed for the device-code demo client)
If you use
AUTH_MODE=proxy, also add a Web redirect URI:http://127.0.0.1:8000/auth/callback
Manifest → set
"requestedAccessTokenVersion": 2insideapi→ SaveCopy Application (client) ID and Directory (tenant) ID
Optional but useful: under Expose an API → Authorized client applications, add any first-party client you control (VS Code, a custom SPA) so users skip a second consent prompt for access_as_user.
2. Run the server locally
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# edit .env
python server.pyServer listens at http://127.0.0.1:8000/mcp (streamable HTTP).
Auth modes
| When to use it |
| You already have an Entra access token for |
| MCP hosts that speak the MCP OAuth spec and expect Dynamic Client Registration. FastMCP's |
| Amazon Bedrock AgentCore Runtime validates the Entra JWT at the edge. FastMCP does not attach a second verifier. |
3. Call it with the demo client
In a second terminal, same .venv and .env:
python client_demo.pyComplete the device-code login in a browser. The client requests only the MCP scope. Graph access happens later, inside the server, via OBO.
You should see whoami_mcp report audience_matches_mcp_app: true, then whoami_graph report a Graph aud, the same oid, and claim_validation.ok: true.
MCP host config (bearer)
{
"mcpServers": {
"entra-obo": {
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer <entra-access-token-for-mcp-app>"
}
}
}
}Do not paste a Graph token into that header. Entra will reject the OBO exchange with AADSTS50013.
4. OBO claim validation
After MSAL returns the Graph token, EntraOboClient.validate_obo_pair checks:
Check | Expect |
inbound | not |
inbound vs Graph | same user |
inbound vs Graph | same tenant |
Graph |
|
Graph | this MCP app client id |
Inbound signature / issuer / lifetime for AUTH_MODE=bearer is FastMCP AzureJWTVerifier. On AgentCore that job is the platform custom JWT authorizer. inspect_assertion is decode-only and is not the security gate.
5. Deploy to Amazon Bedrock AgentCore with AWS CDK
AgentCore hosts FastMCP when the container listens on 0.0.0.0:8000 and serves POST /mcp over stateless streamable HTTP. The image is linux/arm64.
Inbound auth is Entra v2 OIDC:
https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration
allowedAudience is the MCP app client id / api://<client-id>. The runtime allowlists Authorization so the same user token reaches MSAL OBO.
Prerequisites
AWS CDK v2 (
npm i -g aws-cdk), Docker, AWS credentialsEntra app from section 1
A Secrets Manager secret whose string is the Entra client secret (plain string or JSON with
client_secret)
aws secretsmanager create-secret \
--name entra-obo-mcp/client-secret \
--secret-string 'your-entra-client-secret'Deploy
cd infrastructure/cdk
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cdk bootstrap # first time in the account/region
cdk deploy EntraOboMcpStack \
-c entra_tenant_id=<TENANT_ID> \
-c entra_client_id=<CLIENT_ID> \
-c entra_identifier_uri=api://<CLIENT_ID> \
-c entra_secret_name=entra-obo-mcp/client-secretOutputs include AgentRuntimeArn and InvokeUrl:
https://bedrock-agentcore.<region>.amazonaws.com/runtimes/<url-encoded-arn>/invocations?qualifier=DEFAULT
Point client_demo.py at that URL by setting MCP_BASE_URL to the invoke URL without a trailing /mcp if the AgentCore data plane already maps /invocations to the container /mcp. If your client requires an explicit /mcp path, append it and test with MCP Inspector.
export MCP_BASE_URL='https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/<encoded-arn>/invocations?qualifier=DEFAULT'
export AUTH_MODE=bearer # client still uses device code against Entra
python client_demo.pyThe runtime itself must run with AUTH_MODE=agentcore (the Dockerfile and CDK stack set that).
Why the extra hop
Tokens are resource-bound. Graph will not accept a token whose
audis your MCP app.Forwarding the user's raw token is a confused-deputy risk and breaks Conditional Access / token binding.
OBO keeps
oid/subas the user and records the MCP app asazp.Consent for Graph lives on the MCP app registration, not on whatever host launched the agent.
AgentCore Identity validating the inbound JWT is not a substitute for Entra OBO.
Common errors
Symptom | Cause |
| Assertion |
| No delegated grant for Graph on this app. Admin-consent |
| Mixed |
Guest user gets a token for the wrong tenant | OBO used |
OBO on a daemon token | OBO is user-delegation only. App-only traffic should use client credentials. |
No bearer token on AgentCore | Runtime did not allowlist |
Image pull / exec format error | Image was not built for |
Code map
server.py— FastMCP tools and auth wiring (bearer/proxy/agentcore)obo.py— MSALacquire_token_on_behalf_of, audience check, claim pair validation, Graph callsclient_demo.py— device-code login for the MCP audience, then tool callsconfig.py— environment, including Secrets Manager secret ARNDockerfile— AgentCore contract (0.0.0.0:8000,AUTH_MODE=agentcore)infrastructure/cdk— ARM64 image, IAM role,CfnRuntimewith Entra custom JWT
The raw Entra form MSAL sends:
POST https://login.microsoftonline.com/{tid}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id={mcp-app-id}
&client_secret=***
&assertion={inbound-mcp-access-token}
&scope=https://graph.microsoft.com/User.Read
&requested_token_use=on_behalf_ofThis server cannot be deployed
Maintenance
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
One MCP endpoint for your company's tools, with per-person sign-in, policy and audit.
Remote MCP for A2A caller identity, scope policy, verdict receipts, and audit history.
An authenticated remote MCP server for user-owned devices and one-shot capability invocation.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceImplements the on-behalf-of flow with Entra ID to enable authorized calls to Microsoft Graph API via MCP tools.9-
- AlicenseAqualityFmaintenanceProvides secure OAuth2-based credential management for MCP servers, allowing agents to obtain short-lived token references without exposing raw secrets.73 npmMIT
- FlicenseNot gradedqualityCmaintenanceMCP server that uses Microsoft Entra OAuth 2.0 On-Behalf-Of flow to access Microsoft Graph for Outlook data, enabling email, calendar, and contacts interactions via MCP tools.-
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to fetch the signed-in Microsoft Entra user's profile from Microsoft Graph via the GET /me endpoint using the provided bearer token.Apache 2.0