AI Architecture

Building a Secure MCP Gateway for Real Estate AI

Learn how we implemented a first-class MCP (Model Context Protocol) server and OAuth gateway for our real estate AI stack with enterprise-grade authentication using Azure AD B2C.

By Ahmed Abdelhamid
MCP Gateway for Real Estate AI

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

  1. The user opens an MCP-enabled client (e.g., Claude.ai, ChatGPT, or a custom Claude Code terminal) and connects to our MCP server.
  2. The client is sent through our OAuth gateway which proxies to Azure AD B2C for login.
  3. On success, the MCP gateway issues a signed access token and injects the user_id into the request context.
  4. 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
    The user can also request protected tools that need authentication:
    • Quick_Assess_Property
    • Get_Assessed_Properties
    • Get_Property_Assessments
  5. 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.
  6. 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 Architecture Diagram

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 via list_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 Sequence Diagram

MCP OAuth Authentication Sequence

Below is the exact sequence the system follows when an MCP client needs to access protected tools:

  1. 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.
  2. Authorization request (proxy to Azure AD B2C)
    • Client calls the MCP gateway's /oauth/authorize with: client_id, redirect_uri (client's own), state, optional code_challenge, and scope.
    • The gateway encodes a JSON state containing state, redirect_uri, client_id, scope, and code_challenge.
    • The gateway redirects the browser to Azure AD B2C's authorize endpoint, using a single pre-registered redirect_uri that points back to the MCP gateway.
  3. 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 (including id_token).
    • The gateway extracts the user sub (subject) from the id_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's redirect_uri with ?code=...&state=....
  4. Client exchanges code with MCP gateway
    • The MCP client calls the gateway's /oauth/token with grant_type=authorization_code, the received code, client_id, and optionally code_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 array
      • sub = user subject (if present) or client id (client credentials)
  5. 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 sets current_user_id in a contextvars.ContextVar for downstream tool functions.
    • Tool functions such as Get_Assessed_Properties and Get_Property_Assessments read the current user id via get_current_user_id() and enforce per-user data access.

Azure AD B2C details you must configure:

  • App registration with AZURE_B2C_CLIENT_ID and secret; set AZURE_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.