In this article, we'll walk through how we implemented a first-class MCP (Model Context Protocol) server and OAuth gateway for our real estate AI stack. The goal is to let LLM clients securely call domain tools (Kadaster, energy labels, location analysis, user assessments) with clean contracts, stateless sessions, and enterprise-grade authentication—without sacrificing developer speed.
Here's what we focus on: we'll outline the generic MCP architecture, then lean into authentication and our Azure AD B2C integration—the bit that's both tricky and critical when tools touch user-specific data. If you want to skip ahead, see Authentication Flow (Sequence) and OAuth Proxy Endpoints and Azure AD B2C Mapping.
The User Journey
- The user opens an MCP-enabled client (e.g., Claude.ai, ChatGPT, or a custom Claude Code terminal) and connects to our MCP server.
- The client is sent through our OAuth gateway which proxies to Azure AD B2C for login.
- On success, the MCP gateway issues a signed access token and injects the user_id into the request context.
- After authentication, the client lists available tools and their input schemas.
Public tools include:
- Get_Addresses_for_Postcode
- Analyze_Property_Location
- Get_Property_Energy_Label
- Get_Property_Data
- Quick_Assess_Property
- Get_Assessed_Properties
- Get_Property_Assessments
- When invoked from the MCP client as directed by the LLM, the tool runs server-side, queries our Kadaster integration and other services, and returns structured JSON output.
- The client renders the results in the chat, and can chain to other tools if needed.
This keeps the LLM experience simple while enforcing data access rules and producing consistent, tool-friendly outputs.
Solution Architecture
High-level

MCP Gateway Architecture Overview
What actually runs
- ASGI app (Starlette + Uvicorn) with MCP server core.
- StreamableHTTPSessionManager in stateless mode to serve MCP over HTTP.
- OAuth endpoints served by the same app:
/.well-known/oauth-authorization-server
,/oauth/authorize
,/oauth/token
,/oauth/callback
,/oauth/jwks
, plus.well-known/oauth-protected-resource
for resource discovery. - Tool contracts are defined centrally in an
all_tools_schema.json
file and registered dynamically vialist_tools()
. call_tool()
dispatches by name to strongly-typed implementations and invokes the corresponding tool.
Authentication: OAuth Gateway Proxying to Azure AD B2C
If you've worked with Azure AD B2C, you know redirect URI management can slow you down. We needed dynamic client onboarding and zero-portal-changes when testing with new MCP clients. That's why we implemented a gateway pattern.
- We surface protected resource metadata at
/.well-known/oauth-protected-resource
so clients can discover scopes and authorization servers. - We support a minimal Dynamic Client Registration (DCR) endpoint that returns static client credentials to simplify development.
- In Azure AD B2C, we register a single callback URL that points back to our MCP auth server after authentication is successful.
- In the callback endpoint, we encode the original client redirect URI (and context) in OAuth
state
. - On callback, we exchange the code with B2C, extract user identity, then issue our own access token (JWT) that's audience-bound to the MCP resource URL.
This preserves Azure AD B2C's strengths (authentication, policies) while giving us the flexibility to onboard new MCP clients instantly.
Why a Gateway: Evolution and Rationale
A quick origin story on why we put a proxy in front of Azure AD B2C.
Initial approach: direct Azure AD B2C integration
- OAuth endpoints (authorize, token, callback, JWKS) pointed straight at Azure AD B2C.
- Clients were registered in Azure AD B2C with fixed redirect URIs.
- The MCP server acted purely as a resource server, validating Azure-issued tokens via Azure's JWKS.
Pros
- Strong security and standards compliance out of the box.
- Azure scales and manages the identity layer.
Cons
- Redirect URI management for every client/environment slowed us down.
- Dynamic client onboarding and custom flows were awkward or required portal changes.
The problem: redirect URI rigidity
Business needs:
- Onboard new clients and environments quickly, often with unique redirect URIs.
- Support multi-tenant scenarios and rapid prototyping without portal churn.
Technical challenge:
- Azure AD B2C strictly validates redirect URIs; anything not pre-registered is rejected.
The solution: an OAuth proxy/gateway
- The MCP gateway exposes the OAuth surface to clients and proxies to Azure AD B2C using a single, pre-registered callback.
- We encode the original client redirect URI and context into the OAuth state.
- On callback, we exchange the code with Azure, extract user info, and then issue our own access tokens for the MCP resource.
- We host a JWKS endpoint (move to asymmetric keys for production) for token verification.
Why the change?
- We preferred direct Azure for simplicity, but business and multi-tenant demands required agility.
- The gateway pattern was the practical way to preserve Azure AD B2C for authentication while giving MCP the flexibility to evolve.
OAuth Proxy Endpoints and Azure AD B2C Mapping
Think of these endpoints as the "front door" your MCP gateway exposes to any OAuth-capable client. Together, they enable three moments: discovery, user login, and token issuance (plus key discovery for verification). Below we describe each endpoint in plain English first, then show how it lines up with Azure AD B2C as an identity provider.
1) GET /.well-known/oauth-protected-resource
- What it is: A resource metadata document (RFC 9728) that tells clients what "resource" they're trying to access, which authorization servers can issue tokens for it, and which scopes exist.
- Why it matters: It lets an MCP client discover your security story without hardcoding endpoints. The client can learn "who can give me a token for this server and what scopes are available?"
- Typical fields: resource identifier (URL), list of authorization servers, optional docs link and scopes.
- In our gateway, this entails: The resource is the MCP server's base URL; the authorization servers list contains our public gateway issuer. Scopes reflect broad tool capabilities (e.g., read-only lookups vs. user-specific actions) rather than every individual endpoint.
2) GET /.well-known/oauth-authorization-server
- What it is: Standard OAuth/OIDC discovery (a machine-readable index of your auth endpoints and features).
- Why it matters: Clients programmatically learn where to send the browser for authorization, where to POST the code exchange, which code challenge methods (PKCE) you support, and where to fetch signing keys (JWKS).
- Typical fields: issuer, authorization_endpoint, token_endpoint, jwks_uri, registration_endpoint, supported grant types and code challenge methods.
- In our gateway, this entails: The issuer is our public domain; the authorization and token endpoints are the gateway's own
/oauth/authorize
and/oauth/token
; the JWKS URI points to our/oauth/jwks
. Registration is available for developer onboarding.
Authentication Flow (Sequence)

MCP OAuth Authentication Sequence
Below is the exact sequence the system follows when an MCP client needs to access protected tools:
- Client discovers OAuth endpoints
- The MCP client reads
/.well-known/oauth-protected-resource
on the MCP gateway to discover supported scopes and the authorization server. - It also reads
/.well-known/oauth-authorization-server
to learn about authorization_endpoint, token endpoint, and jwks_uri.
- The MCP client reads
- Authorization request (proxy to Azure AD B2C)
- Client calls the MCP gateway's
/oauth/authorize
with:client_id
,redirect_uri
(client's own),state
, optionalcode_challenge
, andscope
. - The gateway encodes a JSON state containing
state
,redirect_uri
,client_id
,scope
, andcode_challenge
. - The gateway redirects the browser to Azure AD B2C's
authorize
endpoint, using a single pre-registeredredirect_uri
that points back to the MCP gateway.
- Client calls the MCP gateway's
- User authenticates with Azure AD B2C
- The user completes login and policies on Azure AD B2C.
- Azure AD B2C calls back the MCP gateway at
/oauth/callback?code=...&state=...
. - The gateway decodes the state, exchanges the
code
at Azure's token endpoint to obtain tokens (includingid_token
). - The gateway extracts the user
sub
(subject) from theid_token
(falling back to unverified claims if strict validation fails), stores an intermediate authorization code for the original client, and redirects the browser back to the original client'sredirect_uri
with?code=...&state=...
.
- Client exchanges code with MCP gateway
- The MCP client calls the gateway's
/oauth/token
withgrant_type=authorization_code
, the receivedcode
,client_id
, and optionallycode_verifier
. - The gateway validates the code (and PKCE if present), marks it used, and issues an MCP access token via
create_access_token()
. The token includes:aud
=MCP_SERVER_URL
(audience bound)scp
= scopes arraysub
= user subject (if present) or client id (client credentials)
- The MCP client calls the gateway's
- Calling protected MCP tools
- The client sends requests with
Authorization: Bearer <access_token>
to the MCP server root where MCP endpoints are mounted. - The authorisation middleware validates the token (issuer and audience), injects
user_id
into the ASGI scope, and setscurrent_user_id
in acontextvars.ContextVar
for downstream tool functions. - Tool functions such as
Get_Assessed_Properties
andGet_Property_Assessments
read the current user id viaget_current_user_id()
and enforce per-user data access.
- The client sends requests with
Azure AD B2C details you must configure:
- App registration with
AZURE_B2C_CLIENT_ID
and secret; setAZURE_B2C_REDIRECT_URI
to the proxy callback URL. - User flow/policy name (
AZURE_B2C_POLICY
) used in authorize/token endpoints. - Ensure the proxy domain (OAUTH_DOMAIN) is reachable and consistent (used as
iss
in MCP tokens and listed in metadata).
Conclusion
MCP made it straightforward to expose our real estate domain capabilities in a way that LLMs can understand and orchestrate. With the OAuth gateway in front of Azure AD B2C, we meet enterprise security requirements without blocking developer velocity. The result is a fast, safe, and extensible path to production-grade agentic workflows.