Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Practical MCP Server Boilerplate for Azure Entra Integration

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.

Why This Exists

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

Multi-Client Authentication Concept

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.

MCP Multi Client OAuth Concept

This diagram intentionally simplifies the flow to highlight the client-compatibility problem rather than the full OAuth protocol details.

How This Differs from Azure's Official Sample

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

Azure Entra ID and OAuth Proxy Scope

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-resource and /.well-known/oauth-protected-resource/mcp expose protected resource metadata for the MCP resource endpoint
  • /.well-known/oauth-authorization-server exposes the local proxy authorization server metadata
  • /register accepts client registration requests from MCP clients and maps them to the configured Azure App Registration
  • /authorize forwards the authorization request to Azure Entra ID with the expected full api:// scope
  • /token exchanges 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.

OAuth Client Compatibility Notes

This template includes a few compatibility behaviors that matter for real MCP clients:

  • The 401 WWW-Authenticate response points to /.well-known/oauth-protected-resource/mcp, matching the MCP endpoint path /mcp.
  • Both /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp return the same metadata document.
  • The protected resource metadata always identifies the MCP endpoint as the resource: https://your-host.com/mcp.
  • The /authorize proxy removes any short-form API scope sent by a client and injects the full Azure Entra scope: api://{AZURE_CLIENT_ID}/{REQUIRED_SCOPE}.
  • The /token proxy removes the incoming resource parameter before calling Azure Entra, because the Azure Entra v2 token endpoint rejects it.
  • The /token proxy adds resource: https://your-host.com/mcp back 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.

Claude.ai DCR Compatibility Note

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_token supplied 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:

  • /register returns a synthetic client registration to the MCP client
  • /authorize maps that synthetic client to the configured Azure Entra App Registration and redirects the user to Azure Entra ID
  • /token proxies the authorization-code exchange to Azure Entra ID
  • /mcp remains 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.

Repository Layout

.
├── 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

Quick Start

cd template
uv sync --extra dev
cp .env.example .env
python dev.py

Then check:

curl http://localhost:8080/health
curl http://localhost:8080/.well-known/oauth-protected-resource
curl http://localhost:8080/.well-known/oauth-protected-resource/mcp

For Docker:

cd template
cp .env.example .env
docker compose up --build
curl http://localhost:8080/health

Configuration

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.

Authentication Modes

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.

Client Integration

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

Verification

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-authenticate

Expected 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.

Included MCP Tools

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

Add Your Own Tool

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

Development Checks

cd template
uv sync --extra dev
uv run pytest

The GitHub Actions workflow runs the same test command on every push and pull request.

Architecture

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
Loading

Roadmap Ideas

  • 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

License

MIT. Use it, fork it, and adapt it for your own MCP projects.

About

A compact Python MCP boilerplate focused on Azure Entra OAuth proxy integration and realistic deployment.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages