Skip to main content
Glama
nhutvl504

quick-entra-bearer-mcp

by nhutvl504

quick-entra-bearer-mcp

Host a third-party MCP/REST API behind Amazon Bedrock AgentCore Gateway, where:

  • the client is Amazon Quick (MCP Actions connector),

  • login is Entra ID (Azure AD) via 3LO — Quick does the OAuth, so every tool call carries a per-user Entra JWT,

  • the third-party API is NOT reached with a static key. Instead a Request Lambda Interceptor reads the user's email claim, calls a special endpoint to mint a per-user bearer token, caches it, and injects it on the outbound hop.

The agent / Quick never sees the bearer token — it is minted and injected inside the Gateway hop, so a prompt-injected model cannot exfiltrate it.

Deploy-ready CDK (Python). Self-contained demo: a stand-in 3rd-party API Lambda lets you exercise the whole path without a real vendor.

Full sequence + per-field config: docs/sequence.md.

Deployment topology (what runs where, trust boundaries): docs/topology.md.

Architecture

Amazon Quick  --3LO Entra, per-user JWT-->  AgentCore Gateway (inbound CUSTOM_JWT = Entra)
                                                   |
                                   REQUEST Lambda Interceptor (passRequestHeaders=true)
                                   1. validate Entra JWT (JWKS cache)
                                   2. read email claim
                                   3. email -> special endpoint -> bearer (cached per email, TTL)
                                   4. REPLACE Authorization: Bearer <3rd>
                                                   |
                                   Gateway Target = OUR backend  (NO vendor credential)
                                                   |
                                   API Gateway (REST) -> Backend Lambda
                                                   |
                                   3rd-party API  (called with the injected bearer)

The Gateway target is a custom backend you own (API Gateway + Lambda), not an OpenAPI target pointed straight at the vendor. The interceptor replaces the Authorization header with the per-user vendor bearer, the Gateway forwards it to the backend (AWS documents this — see docs/caveats.md), and the backend calls the real vendor API. This is the AWS sample's own target shape (a Lambda/REST you own) and keeps the vendor URL server-side.

Why a Request Interceptor and not an ApiKeyCredentialProvider: the vendor bearer is dynamic and per-user, derived at call time from the signed-in user's email. A static credential provider cannot express that. The interceptor is custom code that runs on every tool call, so it can do the email→token exchange and keep the token off the agent. (Pattern from the AWS sample Custom Authentication with Request Lambda Interceptor.)

Why 3LO (not 2LO): we need the email of the user who is chatting. 2LO (client-credentials) runs as a shared service account and carries no user identity. 3LO makes each user sign in to Entra once, so the token carries their email.

Related MCP server: Lark AgentCore Gateway Interceptor MCP Server

Repository layout

app.py                      CDK entrypoint
settings.py                 env-driven config (nothing secret here)
infra/stack.py              THE stack: Gateway (CUSTOM_JWT=Entra) + interceptor + backend(API GW+Lambda) target
src/interceptor/
  lambda_function.py        the Request Interceptor (Entra -> email -> bearer)
src/third_party_api/
  handler.py                demo 3rd-party API (local stand-in for the vendor)
src/backend/
  handler.py                backend behind API Gateway = the Gateway target; calls vendor with injected bearer
scripts/
  build_interceptor.sh      vendor PyJWT[crypto] into the interceptor asset
  create_secret.sh          (optional) secret for the special endpoint's own credential
  deploy.sh                 build + synth + deploy
tests/test_interceptor.py   unit tests for the interceptor logic (no AWS needed)
docs/sequence.md            the full sequence diagram (Mermaid)
docs/topology.md            deployment topology (Mermaid)

Prerequisites

  • AWS account with Bedrock AgentCore Gateway + Identity available, CDK bootstrapped.

  • Python 3.13+ locally (the Lambda runtime is 3.13), Node.js 20+, Docker not required.

  • An Entra ID (Azure AD) app registration (see below).

  • A special endpoint that exchanges { email } → { access_token, expires_in }.

  • The third-party API URL (or use the demo API this stack deploys).

Configure

cp .env.example .env     # fill in your values
source .env

Key fields (full list in .env.example / settings.py):

Var

Meaning

ENTRA_TENANT_ID

Azure AD tenant GUID

ENTRA_AUDIENCE

Application ID URI of your Entra app (the token aud)

EMAIL_CLAIM

where Entra puts the email (email / preferred_username / upn)

THIRD_PARTY_BASE_URL

the vendor API base URL (or the demo API URL after first deploy)

SPECIAL_TOKEN_ENDPOINT

endpoint that turns {email} into a bearer

SPECIAL_ENDPOINT_SECRET_NAME

(optional) Secrets Manager secret the endpoint needs

Deploy

python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
source .env
bash scripts/deploy.sh

deploy.sh vendors PyJWT into the interceptor asset, synthesizes, and deploys. Outputs: GatewayMcpUrl, GatewayArn, InterceptorArn, BackendApiUrl.

The Gateway target validates its endpoint at deploy time by calling tools/list. A wrong URL or a rejected credential fails the deployment rather than 401ing in production — verify your endpoint first.

Wire Amazon Quick (Custom OAuth app = Entra, 3LO)

Step-by-step with the two app registrations (client + resource), the exact fields, and the AADSTS troubleshooting table: docs/entra-setup.md. One Entra user is enough to demo.

In Amazon Quick → Integrations → Actions → Model Context Protocol → new integration:

  • MCP server endpoint = the GatewayMcpUrl output.

  • Authentication = Custom OAuth app (3LO):

    • Client ID / Secret = your Entra app registration

    • Authorization URL = https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize

    • Token URL = https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token

    • Redirect URL = https://<region>.quicksight.aws.amazon.com/sn/oauthcallback

    • Scope = openid email profile + your Gateway API scope

Entra app registration must: add that redirect URI, expose an API scope (so the aud matches ENTRA_AUDIENCE), and emit the email claim.

Test the path end-to-end (demo)

The Gateway target is the backend (API Gateway + Lambda); the backend calls whatever THIRD_PARTY_BASE_URL points at. To exercise it without a real vendor, deploy the demo vendor Lambda and set THIRD_PARTY_BASE_URL to its Function URL, then from a Quick chat invoke the get_customer_data tool. Confirm in CloudWatch: interceptor logs show the Authorization header replaced with the per-user bearer, and the backend logs show the vendor call succeeding with that bearer.

See docs/caveats.md for what is AWS-documented vs. to-verify, and the Entra AADSTS gotchas.

Run unit tests

pip install pytest
python3 -m pytest tests/ -q

The tests stub AWS + network and cover: output-shape helpers, email extraction, token-cache hit/miss, and the handler control flow (missing bearer → 401, no email → 403, happy path injects Bearer <downstream>).

Teardown

npx aws-cdk@latest destroy quick-entra-bearer-mcp-dev

Security notes

  • The downstream bearer never reaches Quick or the model — minted + injected in the interceptor on the Gateway→vendor hop.

  • Inbound is Entra JWT (CUSTOM_JWT authorizer); only valid Entra tokens get in.

  • The interceptor Lambda is invokable only by the Gateway service principal.

  • If the special endpoint needs its own credential, it lives in Secrets Manager and only the interceptor role can read it (scoped by name).

  • Make the interceptor idempotent (the Gateway may retry); the per-email cache does this and avoids minting a token on every call.

License

MIT-0.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    A client-side MCP proxy that injects OAuth 2.0 bearer tokens or API keys into MCP requests, enabling MCP clients to connect to OAuth/API-key-protected MCP servers like Amazon Bedrock AgentCore Gateway. It automatically fetches and refreshes credentials using AgentCore Identity or static values.
    MIT No Attribution
  • A
    license
    Not graded
    quality
    C
    maintenance
    Remote and centralized MCP server that enables AI models to interact with Microsoft Fabric (workspaces and items) via the Fabric Core REST API, using delegated OAuth 2.1 authentication with Microsoft Entra ID so each user's permissions and restrictions are respected.
    GPL 3.0