An end-to-end demo of OAuth identity passthrough for MCP tools in Microsoft Foundry Agent Service — the agent invokes a remote MCP server as the signed-in user, not as a shared service account. Users sign in with Microsoft Entra ID, consent once on first tool use, and every later tool call runs with their own credentials. The Atlassian MCP server here is just the downstream example, any OAuth-compliant MCP server works the same way.
- Per-user OAuth to MCP tools: on a user's first tool call, Foundry returns an
oauth_consent_requestwith a consent link. After sign-in and consent, credentials are stored by Agent Service and reused — no re-consent on later calls. - Caller identity mapping end to end: frontend Entra token → backend creates the Foundry client with the user's token → Foundry keeps consent state and credentials per real user → the downstream system (Jira/Confluence) enforces that user's permissions.
- Human-in-the-loop tool approval: with
require_approvalenabled, each MCP tool call surfaces anmcp_approval_requestthat the user approves or denies in the UI. - Re-runnable first-use flow: a helper script recreates the MCP project connection to invalidate stored credentials, so you can demo the consent flow as many times as you want.
- The user signs in with Microsoft Entra ID in the React frontend (MSAL, scope
https://ai.azure.com/.default). - The frontend sends the chat message to the FastAPI backend with the user's bearer token.
- The backend validates the JWT (signature, audience, issuer, expiry) and creates the Foundry
AIProjectClientwith the user's token — so consent state and stored credentials are isolated per user. It then calls the Foundry Responses API with anagent_reference. - On the user's first MCP tool call, Foundry returns an
oauth_consent_request. The UI shows the consent link; the user completes sign-in and consent with the OAuth provider, then clicks "I completed sign-in", and the backend resumes the run viaprevious_response_id. - Foundry calls the MCP server carrying that user's stored credentials. If
require_approvalis on, anmcp_approval_requestis surfaced first and the backend submits the user's approve/deny decisions. - The MCP server calls Atlassian Cloud as the signed-in user and the agent answers from the tool results.
- An Azure AI Foundry project with a model deployment
- A Microsoft Entra app registration (SPA) for the frontend sign-in
- Users in the same tenant as the Foundry project, with at least the Foundry Agent Consumer role on the project
- A remote MCP server that supports OAuth (this demo uses mcp-atlassian with an Atlassian OAuth 2.0 app)
- An MCP project connection in your Foundry project configured with OAuth Identity Passthrough (client ID/secret, auth/token/refresh URLs, scopes — include
offline_accessfor auto refresh)
The container builds the frontend, and the backend serves it and injects runtime config via env-config.js, production deployment only needs one container.
docker run -itd -p 8765:8765 --name FoundryMCPOAuthPassthrough \
--restart unless-stopped \
-e MSAL_CLIENT_ID="your-azure-ad-client-id" \
-e AZURE_TENANT_ID="your-azure-ad-tenant-id" \
-e FOUNDRY_PROJECT_ENDPOINT="https://your-foundry-account.services.ai.azure.com/api/projects/your-foundry-project" \
-e FRONTEND_REDIRECT_URI="https://your-app-domain" \
-e AGENT_NAME="FoundryAtlassianAgent" \
ghcr.io/heyjiqingcode/foundrymcpoauthpassthrough:1.0.0The app is then available at http://localhost:8765.
FRONTEND_REDIRECT_URImust match a redirect URI registered on the Entra app; it defaults to the page's own origin when unset.
See Configuration for every available variable.
1)Set up
# Clone code and install requirements
git clone https://github.com/HeyJiqingCode/FoundryMCPOAuthPassthrough.git
cd FoundryMCPOAuthPassthrough
source .venv/bin/activate
pip install -r requirements.txt
cd frontend && npm install && cd ..
# Copy .env.example and fill in your Entra / Foundry / MCP settings
cp .env.example .env2)Create the Foundry Agent
python scripts/create_foundry_agent.pyThe script connects with
DefaultAzureCredential, deletes the latest version ofAGENT_NAMEif it exists, and creates a fresh version with the configured model, reasoning effort, and the MCP tool (server_url+project_connection_id+require_approval).
3)Run frontend and backend separately
# Terminal 1 — frontend on http://localhost:3500
cd frontend
npm start
# Terminal 2 — backend on http://localhost:8765
uvicorn backend.foundry_agent_server:app --host 0.0.0.0 --port 8765 --reload
# Then open http://localhost:3500
frontend/start-dev.shloads values from the root.envfor local CRA development.
See Configuration for every available variable.
- Sign in with an Entra account and ask something like "show my open Jira issues".
- First MCP call → a "Sign in required (OAuth consent)" card appears. Open the consent link, complete the Atlassian sign-in and consent, then click "I completed sign-in" — the agent resumes automatically.
- With
AGENT_MCP_REQUIRE_APPROVAL=always, review each tool call (server, tool name, arguments) and click "Submit approvals". - Sign in as a second user: they get their own consent prompt, and their results reflect their own Atlassian permissions — that's the passthrough.
Consent state lives with the MCP project connection, so recreating the connection invalidates all stored user credentials and every user is prompted again — useful for re-running the first-use demo:
# Preview the ARM payload without changing anything
python scripts/reset_mcp_project_connection.py --dry-run
# Delete and recreate the connection (backs up to .tmp/project-connection-backups first)
python scripts/reset_mcp_project_connection.py --yesThe script reads the existing connection via ARM, backs it up, deletes it, waits for the deletion to complete, and recreates it with the same name and ARM ID. All OAuth settings are inherited from the existing connection unless overridden by CLI flags or MCP_OAUTH_* environment variables.
| Variable | Used by | Description |
|---|---|---|
AZURE_TENANT_ID |
backend, frontend | Entra tenant ID. Backend validates token issuer and signing keys against it. Required. |
MSAL_CLIENT_ID |
frontend | Client ID of the Entra app registration used for sign-in. |
FRONTEND_REDIRECT_URI |
frontend | MSAL redirect URI. Defaults to the page's own origin. |
BACKEND_URL |
frontend | Backend base URL. Defaults to the page's own origin — leave empty for the single-container deployment. |
CORS_ALLOWED_ORIGINS |
backend | Comma-separated allowed origins. Default: http://localhost:3500,http://127.0.0.1:3500. |
FOUNDRY_PROJECT_ENDPOINT |
backend, scripts | Foundry project endpoint. Required. |
AGENT_NAME |
all | Foundry Agent name. Default: FoundryAtlassianAgent. |
AGENT_MODEL |
create script | Model deployment name for the agent. |
AGENT_REASONING_EFFORT |
create script | Reasoning effort applied to the agent. Default: high. |
AGENT_MCP_REQUIRE_APPROVAL |
create script | always, never, or a per-tool JSON. Foundry defaults to always when unset. |
MCP_TOOL_SERVER_NAME |
scripts | MCP server_label, also used as the connection name fallback. |
MCP_TOOL_SERVER_URL |
scripts | Remote MCP server endpoint, e.g. https://<host>/mcp. |
MCP_PROJECT_CONNECTION_ID |
scripts | Full ARM resource ID of the MCP project connection. |
MCP_OAUTH_CLIENT_ID / MCP_OAUTH_CLIENT_SECRET / MCP_OAUTH_AUTH_URL / MCP_OAUTH_TOKEN_URL / MCP_OAUTH_REFRESH_URL / MCP_OAUTH_SCOPES |
reset script | Optional overrides; inherited from the existing connection when unset. |
- Consent is scoped per user × per tool connection × per Foundry project — a new user or a new connection triggers a new consent prompt.
- Include
offline_accessin the connection scopes so Agent Service can refresh tokens automatically. - Cross-tenant token exchange is not supported: users must be in the same Entra tenant as the Foundry project.
- Agent Service supports managed OAuth and custom OAuth; this demo uses custom OAuth (bring your own OAuth app registered with the downstream service).
