A compact, domain-neutral Python template for experimenting with Model Context Protocol (MCP) servers that use FastAPI, FastMCP, Azure Entra ID integration, API-key fallback, health checks, Docker packaging, and client setup notes.
This project is designed for builders who want a small, readable starting point for moving beyond a local MCP demo while still being able to replace the sample tools with their own business or IT workflows.
Most MCP examples are intentionally tiny. That is great for learning the protocol, but less helpful when you need to expose a server over HTTP, support OAuth discovery, keep API-key access for automation, and explain the deployment shape to another engineer.
Microsoft also provides more complete Azure-hosted MCP samples. This repository takes a narrower approach: it is a lightweight boilerplate meant to be easy to read, fork, and adapt.
This boilerplate packages those practical pieces into a small template:
- FastMCP tool definitions with FastAPI HTTP hosting
- Azure Entra ID integration through a lightweight OAuth proxy
- API key authentication for local development and automation
- OAuth discovery endpoints for MCP-capable clients
- Streamable HTTP mounting with path normalization
- Health and runtime metrics endpoint
- Docker and Compose support
- Integration docs with screenshots for popular clients
- Tests and GitHub Actions CI so forks start with a quality bar
The same MCP server can behave differently depending on the client connecting to it.
Claude, Postman, Copilot Studio, and Azure AI Foundry each have slightly different expectations around OAuth discovery, registration, redirect handling, token flow, and resource binding.
This repository experiments with a lightweight OAuth compatibility layer that bridges those client expectations with Azure Entra ID.
Conceptual illustration only — simplified for readability.
This diagram intentionally simplifies the flow to highlight the client-compatibility problem rather than the full OAuth protocol details.
Microsoft provides Azure-Samples/remote-mcp-webapp-python-auth-oauth, a full FastAPI MCP Weather Server sample with OAuth 2.1, PKCE, Dynamic Client Registration, Azure AD integration, a web test interface, and Azure App Service deployment through azd.
This repository is intentionally smaller and more domain-neutral. It is not trying to replace the Azure sample. Instead, it focuses on a reusable boilerplate shape:
| Area | Azure official sample | This repository |
|---|---|---|
| Primary goal | Full Azure-hosted sample application | Compact boilerplate / starter template |
| Domain | Weather tools | Domain-neutral starter tools |
| Deployment focus | Azure App Service with azd |
Local-first Docker / Compose, adaptable deployment shape |
| Auth focus | Full OAuth 2.1 sample flow with PKCE and DCR | Azure Entra OAuth proxy bridge plus resource-server request validation |
| Tooling | Built-in web OAuth test interface | Minimal runtime with system design and client integration notes |
| Best for | Learning and deploying the official Azure sample app | Forking into your own MCP server PoC or business-tool template |
The official MCP SDK examples are the best place to learn the core protocol mechanics, including OAuth flows, protected resource metadata, client registration, and token validation patterns.
This boilerplate has a narrower practical focus: integrating an MCP server with Azure Entra ID while keeping the server easy to run, package, and extend.
Some MCP-capable clients expect OAuth discovery and Dynamic Client Registration-style endpoints, while Azure Entra uses a static App Registration model. To bridge that gap, this project includes a lightweight OAuth proxy layer:
/.well-known/oauth-protected-resourceand/.well-known/oauth-protected-resource/mcpexpose protected resource metadata for the MCP resource endpoint/.well-known/oauth-authorization-serverexposes the local proxy authorization server metadata/registeraccepts client registration requests from MCP clients and maps them to the configured Azure App Registration/authorizeforwards the authorization request to Azure Entra ID with the expected fullapi://scope/tokenexchanges authorization codes through Azure Entra ID and returns the resulting tokens to the client
The MCP server still validates incoming requests as a resource server: bearer tokens are verified as Azure Entra JWTs, and API keys remain available for local development, automation, or simple private deployments.
This template includes a few compatibility behaviors that matter for real MCP clients:
- The 401
WWW-Authenticateresponse points to/.well-known/oauth-protected-resource/mcp, matching the MCP endpoint path/mcp. - Both
/.well-known/oauth-protected-resourceand/.well-known/oauth-protected-resource/mcpreturn the same metadata document. - The protected resource metadata always identifies the MCP endpoint as the resource:
https://your-host.com/mcp. - The
/authorizeproxy removes any short-form API scope sent by a client and injects the full Azure Entra scope:api://{AZURE_CLIENT_ID}/{REQUIRED_SCOPE}. - The
/tokenproxy removes the incomingresourceparameter before calling Azure Entra, because the Azure Entra v2 token endpoint rejects it. - The
/tokenproxy addsresource: https://your-host.com/mcpback into successful token responses so clients can associate the token with the MCP resource.
These behaviors help avoid common failures such as Postman repeatedly receiving 401 responses after login, clients requesting both short-form and full API scopes, or Azure Entra returning AADSTS70011 for invalid scope combinations.
As of 2026-05-13, Anthropic's public documentation and support materials indicate that remote MCP usage has two related but different patterns:
- Claude.ai custom connectors can use OAuth-based remote MCP servers and support Dynamic Client Registration-style flows.
- Anthropic's Messages API MCP connector can accept an
authorization_tokensupplied by the API caller after the caller obtains a token through its own OAuth flow.
This distinction matters for this project. The /register, /authorize, and /token routes are primarily included for MCP clients such as Claude.ai custom connectors that discover OAuth metadata and expect an MCP-compatible registration and authorization surface.
Azure Entra ID does not dynamically create a new App Registration for every MCP client. This project therefore maps the client-facing DCR-style flow to a preconfigured Azure App Registration:
/registerreturns a synthetic client registration to the MCP client/authorizemaps that synthetic client to the configured Azure Entra App Registration and redirects the user to Azure Entra ID/tokenproxies the authorization-code exchange to Azure Entra ID/mcpremains the protected MCP resource endpoint and validates the resulting bearer token
In other words, this project does not replace Azure Entra ID. It provides the MCP-facing OAuth compatibility layer that some clients expect, while delegating identity and token issuance to Azure Entra ID.
.
├── template/ # Copy this folder to start a new MCP server
│ ├── src/server.py # MCP tools live here
│ ├── src/http/ # FastAPI app, OAuth proxy, middleware
│ ├── tests/ # Starter tests
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── pyproject.toml
├── docs/
│ ├── CLIENT_INTEGRATION.md # Claude, Copilot Studio, Azure AI Foundry notes
│ ├── SYSTEM_DESIGN.md # Design rationale and request flow
│ └── screenshots/ # Setup screenshots
└── README.md
cd template
uv sync --extra dev
cp .env.example .env
python dev.pyThen check:
curl http://localhost:8080/health
curl http://localhost:8080/.well-known/oauth-protected-resource
curl http://localhost:8080/.well-known/oauth-protected-resource/mcpFor Docker:
cd template
cp .env.example .env
docker compose up --build
curl http://localhost:8080/health| Variable | Required | Description |
|---|---|---|
BASE_URL |
Yes | Public URL of this server, no trailing slash |
API_KEYS |
Yes | Comma-separated keys accepted by x-api-key auth |
SERVICE_NAME |
No | Display name returned by metadata endpoints |
SERVICE_OWNER |
No | Owner or organization name |
SERVICE_VERSION |
No | Version displayed by root, health, and tools |
AZURE_TENANT_ID |
OAuth | Azure Entra tenant ID |
AZURE_CLIENT_ID |
OAuth | Azure App Registration client ID |
REQUIRED_SCOPE |
OAuth | Scope name exposed by the App Registration, without the api:// prefix. Default: MCP.Access |
AZURE_CLIENT_SECRET |
Optional | Server-side secret used by the OAuth proxy |
Set BASE_URL=http://localhost:8080 and leave the Azure variables blank to run in API-key-only mode.
For OAuth mode, create an Azure App Registration and expose an API scope that matches REQUIRED_SCOPE. For example, if REQUIRED_SCOPE=MCP.Access, the proxy will request:
api://{AZURE_CLIENT_ID}/MCP.Access
Do not put real tenant IDs, client IDs, client secrets, or API keys into this repository. Keep them in .env, Azure App Service configuration, Key Vault, GitHub Actions secrets, or your deployment platform's secret store.
| Scheme | Header | Best For |
|---|---|---|
| OAuth 2.0 with Azure Entra | Authorization: Bearer {jwt} |
Claude, Postman, Copilot Studio, enterprise users |
| API key | x-api-key: {key} |
Local development, service jobs, simple private deployments |
Public paths are intentionally limited to /, /health, /.well-known/*, /docs, /redoc, /openapi.json, /authorize, /token, and /register.
Use this MCP endpoint:
https://your-host.com/mcp
OAuth endpoints are discovered through:
/.well-known/oauth-protected-resource/.well-known/oauth-protected-resource/mcp/.well-known/oauth-authorization-server
After deployment, these checks should pass:
# Bare protected resource metadata should return the MCP resource URL.
curl -s https://your-host.com/.well-known/oauth-protected-resource | jq .resource
# Path-suffixed protected resource metadata should return the same MCP resource URL.
curl -s https://your-host.com/.well-known/oauth-protected-resource/mcp | jq .resource
# Unauthorized MCP requests should advertise the path-suffixed metadata URL.
curl -sv https://your-host.com/mcp 2>&1 | grep -i www-authenticateExpected resource value:
https://your-host.com/mcp
Expected WWW-Authenticate pattern:
Bearer resource_metadata="https://your-host.com/.well-known/oauth-protected-resource/mcp"
For Claude and Azure client walkthroughs, see docs/CLIENT_INTEGRATION.md. For design decisions, see docs/SYSTEM_DESIGN.md.
| Tool | Description |
|---|---|
whoami |
Returns JWT claims when OAuth is used, or an API-key auth message |
ping |
Echoes a message with a UTC timestamp |
server_profile |
Returns service metadata, auth mode, endpoints, and extension hints |
Edit template/src/server.py:
@mcp.tool()
async def my_tool(param: str) -> dict:
"""Tool description shown to the LLM."""
auth = get_auth()
return {
"input": param,
"auth_type": auth.auth_type if auth else "unknown",
}get_auth() returns an AuthContext with:
auth_type:"bearer"or"api_key"user_oid,user_name,user_upn: populated for OAuth bearer tokens
cd template
uv sync --extra dev
uv run pytestThe GitHub Actions workflow runs the same test command on every push and pull request.
This boilerplate is organized around a small but practical request path: public OAuth discovery, authentication middleware, FastMCP streamable HTTP transport, and extensible tool handlers.
flowchart LR
subgraph Clients["MCP Clients"]
Claude["Claude / Claude Desktop"]
Postman["Postman MCP Client"]
Copilot["Copilot Studio"]
AzureAI["Azure AI Foundry"]
S2S["Server-to-Server Client"]
end
subgraph Discovery["OAuth Discovery & Proxy"]
WellKnown["/.well-known/*"]
Register["/register"]
Authorize["/authorize"]
Token["/token"]
end
subgraph Auth["Authentication"]
AuthMW["AuthenticationMiddleware"]
JWT["JWT Bearer<br/>Azure Entra ID"]
APIKey["API Key<br/>x-api-key"]
Context["AuthContext<br/>ContextVar"]
end
subgraph MCPRuntime["MCP Runtime"]
MCP["/mcp<br/>Streamable HTTP"]
PathMW["MCPPathMiddleware"]
StreamCtrl["StreamConcurrencyController"]
FastMCP["FastMCP Server"]
end
subgraph Tools["Tool Layer"]
BuiltIn["Built-in Tools<br/>whoami / ping / server_profile"]
Custom["Custom Business Tools"]
end
subgraph Systems["Enterprise Systems"]
ITSM["ITSM"]
CMDB["CMDB"]
KB["Knowledge Base / RAG"]
Monitoring["Monitoring"]
APIs["Internal APIs"]
end
Entra["Azure Entra ID"]
Claude --> WellKnown
Postman --> WellKnown
Copilot --> WellKnown
AzureAI --> WellKnown
WellKnown --> Register
Register --> Authorize
Authorize --> Entra
Token --> Entra
Claude --> MCP
Postman --> MCP
Copilot --> MCP
AzureAI --> MCP
S2S --> MCP
MCP --> AuthMW
AuthMW --> JWT
AuthMW --> APIKey
JWT --> Context
APIKey --> Context
Context --> PathMW
PathMW --> StreamCtrl
StreamCtrl --> FastMCP
FastMCP --> BuiltIn
FastMCP --> Custom
Custom --> ITSM
Custom --> CMDB
Custom --> KB
Custom --> Monitoring
Custom --> APIs
- Add deployment guides for Azure Container Apps, Fly.io, or Render
- Add optional OpenTelemetry tracing
- Add a cookiecutter-style project generator
- Add more client compatibility tests
- Add example business tools, such as CRM lookup or internal search
MIT. Use it, fork it, and adapt it for your own MCP projects.
