-
Notifications
You must be signed in to change notification settings - Fork 50
Description
Summary
Add a Model Context Protocol (MCP) server to the existing FastAPI restaurant reviews sample app, secure it with Microsoft Entra ID authentication (EasyAuth v2), and preauthorize Azure AI Foundry agent identities to call the MCP tools using managed identity (ServiceIdentity) authentication via the client_credentials flow.
Motivation
Azure AI Foundry agents need to consume MCP servers hosted on Azure App Service. The existing samples needs to demonstrate how to secure an MCP endpoint for agent identity access to ensure agentic securty risks are minimized. This change provides a working, end-to-end example that developers can follow to:
- Add an MCP server to an existing FastAPI app
- Secure it with Entra ID authentication
- Preauthorize Foundry agent identities (which use
ServiceIdentitytype principals, not standard app registrations)
Changes
New Files
src/fastapi_app/mcp_server.py— MCP server with 4 tools (list_restaurants_mcp,get_details_mcp,create_review_mcp,create_restaurant_mcp) usingFastMCPwithstateless_http=TrueGUIDE_AUTH_MCP_SERVER.md— Comprehensive step-by-step guide covering app registration, EasyAuth configuration, PRM setup, Foundry agent preauthorization, verification, troubleshooting, and 22 test cases
Modified Files
src/pyproject.toml— Addedmcp[cli]to dependenciessrc/fastapi_app/app.py— Imported and mounted MCP server at/mcp, addedmcp_lifespancontext manager to FastAPI appsrc/my_uvicorn_worker.py— Changedlifespanfrom"auto"to"on"(required for MCP session manager to start under gunicorn)README.md— Updated title, description, and added sections for MCP tools, architecture diagram, local MCP verification, Entra ID auth setup steps, Foundry agent integration, and key learningsinfra/main.bicep,infra/resources.bicep— Infrastructure updates fromazd updeployment
Technical Details
MCP Server
- Mounted at
/mcp/mcpunder the existing FastAPI app - Uses
stateless_http=Truefor compatibility with FastAPI mount mcp_lifespancontext manager ensures the MCP session manager starts/stops with the app- Tools use
asyncio.to_thread()to run synchronous SQLModel/SQLAlchemy DB queries
Authentication Architecture
Azure AI Foundry Agent
│
│ client_credentials flow → token with MCP.Access role
▼
Azure App Service (EasyAuth ~2, Return401)
│
│ JWT validated: issuer, audience, allowedClientApplications
▼
FastAPI + gunicorn (lifespan: on)
│
│ /mcp/mcp → FastMCP (stateless_http)
▼
MCP Tools → PostgreSQL
Key Configuration
| Component | Setting | Value |
|---|---|---|
| EasyAuth | runtimeVersion |
~2 (v1 doesn't enforce auth properly) |
| EasyAuth | unauthenticatedClientAction |
Return401 |
| App Role | MCP.Access |
allowedMemberTypes: ["Application"] |
| PRM | WEBSITE_AUTH_PRM_DEFAULT_WITH_SCOPES |
api://<client-id>/user_impersonation |
| Gunicorn | lifespan |
"on" (critical for MCP session manager) |
Key Learnings Documented
ServiceIdentityprincipals (Foundry agents) cannot be added topreAuthorizedApplications— use app role assignments +allowedClientApplicationsinstead- A service principal must be created for the app registration before role assignments work (commonly missed step)
runtimeVersion: "~2"is required for EasyAuth to properly enforce authentication- The PRM endpoint (
/.well-known/oauth-protected-resource) is served by EasyAuth and is exempt from authentication by design
Test Coverage
22 test cases across 3 groups:
- Group A (8 tests): MCP functionality without authentication (auth disabled)
- Group B (11 tests): Authentication enforcement — unauthenticated, fake token, wrong audience, valid token flows
- Group C (3 tests): Python MCP client programmatic tests (with/without auth)
Verification
- Foundry agent (
mslearnagent) successfully connects and enumerates all 4 MCP tools (mcp_list_toolsstatus:OK) - Entra ID sign-in logs show successful
ServiceIdentityauthentication - PRM endpoint serves correct OAuth metadata
- Unauthenticated requests correctly return 401