Accueil / Articles

Enterprise MCP sur Bedrock AgentCore avec Okta SSO et Trino

Cerveau Lambda OAuth, relais PKCE, passerelle et environnement d’exécution AgentCore, ainsi que des connexions Trino par utilisateur sans fuite de tokens partagés.

Éditeur : REACTAPP.TOP13 min de lecture2982 mots

Sources: Building Enterprise MCP on AWS Bedrock AgentCore: Okta SSO + Trino

Ce guide reconstitue le parcours allant des matières premières à un système fonctionnel pour : la création d’un MCP d’entreprise sur AWS Bedrock AgentCore : Okta SSO + Trino. L’accent est mis sur des étapes opérationnelles, des vérifications explicites, ainsi que du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner son intention. Pour une vue d’ensemble, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés.

En résumé

En bref, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Exposez des outils dotés de schémas restreints et de labels explicites indiquant leurs effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant de valider automatiquement.

MCP Client → Lambda OAuth Proxy → Bedrock AgentCore Gateway → AgentCore Runtime → Trino

Pourquoi remettre un jeton brut à un outil fait échouer les vérifications entreprises

Afin de comprendre pourquoi la transmission d’un jeton brut à une outil échoue aux vérifications entreprises, il convient de définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Exposez des outils dotés de schémas restreints et de labels explicites indiquant leurs effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement.

Architecture en bref

Pour une architecture à vue d’ensemble, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Exposez des outils dotés de schémas restreints et de labels explicites concernant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement. Pour une architecture à vue d’ensemble, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés.

1. Le client MCP

Pour le client MCP, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. La configuration doit être conservée en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. L’authentification s’effectue au niveau du gateway, tandis que la réautorisation a lieu au niveau du plan de données. Un simple token porteur ne constitue pas une frontière entre les tenants.

2. Le proxy Lambda MCP (le cerveau OAuth)

Pour le point 2 : le proxy Lambda MCP (le cerveau OAuth), il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une amélioration ultérieure. Authentifiez-vous au niveau du gateway et réautorisez-vous au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants.

3. Le AWS Bedrock AgentCore Gateway

Pour le point 3, concernant le AWS Bedrock AgentCore Gateway, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Préférer des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé. Authentifier au niveau du gateway et réautoriser au niveau du plan de données. Un simple token porteur ne constitue pas une frontière entre les tenants. Pour le point 3, concernant le AWS Bedrock AgentCore Gateway, il convient de définir les entrées, le responsable de l’étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrer les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.

4. L’environnement de exécution AWS Bedrock AgentCore

Pour l’environnement de exécution AWS Bedrock AgentCore, il convient de définir les entrées, le responsable de l’étape et les critères de fin avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. La configuration doit être conservée en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat de chaque appel. Sans ces informations, le débogage des boucles d’agent prend des heures.

Le flux d’autorisation bout en bout

Pour le flux d’authentification bout en bout, définissez les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat de chaque appel. Sans ces traces, déboguer des boucles d’agent prend des heures.

┌─────────────────────┐
│    MCP Client       │
│ Claude / Claude Code│
└──────────┬──────────┘
           │
           │ OAuth 2.1 + PKCE
           ▼
┌─────────────────────┐
│   Lambda MCP Proxy  │◄──────────────► Okta
│ OAuth / Identity    │   Login + JWT
└──────────┬──────────┘
           │
           │ Bearer JWT
           ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Gateway   │
│ CUSTOM_JWT validation       │
└────────────┬────────────────┘
             │ SigV4
             ▼
┌─────────────────────────────┐
│ Bedrock AgentCore Runtime   │
│ FastMCP Server              │
│                             │
│ list_catalogs               │
│ describe_table              │
│ execute_query               │
└────────────┬────────────────┘
             │
             │ User JWT
             ▼
┌─────────────────────────────┐
│ Enterprise Query Engine     │
│ Per-user authorization      │
└─────────────────────────────┘

Approfondissement 1 : Apprendre au proxy Lambda à utiliser OAuth 2.1

Pour l’approfondissement 1 : Apprendre au proxy Lambda à utiliser OAuth 2.1, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat de chaque appel. Déboguer des boucles d’agent sans cette trace gaspille des heures. Pour l’approfondissement 1 : Apprendre au proxy Lambda à utiliser OAuth 2.1, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Notez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le parcours

passe des environnements de démonstration aux environnements partagés.

# GET /.well-known/protected-resource
{
  "resource": f"{BASE_URL}/mcp",
  "authorization_servers": [BASE_URL],
}
# GET /.well-known/oauth-authorization-server
{
  "issuer": BASE_URL,
  "authorization_endpoint": f"{BASE_URL}/authorize",
  "token_endpoint":         f"{BASE_URL}/token",
  "registration_endpoint":  f"{BASE_URL}/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
}

Réseau de transmission de PKCE sans le perturber

Pour réseauter la transmission de PKCE sans la perturber, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les indicateurs fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du schéma. Exposez des outils dotés de schémas restreints et de labels explicites indiquant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement.

relay_payload = json.dumps({"u": original_redirect, "s": original_state}).encode()
relay_state   = base64.urlsafe_b64encode(relay_payload).decode().rstrip("=")
params = {
    "client_id":             OKTA_CLIENT_ID,
    "response_type":         "code",
    "redirect_uri":          f"{BASE_URL}/callback",   # <- proxy's callback
    "state":                 relay_state,               # <- client's state, wrapped
    "code_challenge":        qs["code_challenge"],      # <- untouched
    "code_challenge_method": "S256",
}

Inscription dynamique du client

Pour une inscription dynamique des clients, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Exposez des outils dotés de schémas restreints et de labels explicites indiquant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement.

def handle_register(event):
    req = json.loads(event.get("body") or "{}")
    return _json_resp(201, {
        "client_id": OKTA_CLIENT_ID,
        "redirect_uris": req.get("redirect_uris", []),
        "grant_types":   req.get("grant_types", ["authorization_code"]),
        "response_types": req.get("response_types", ["code"]),
        "token_endpoint_auth_method": "none",
    })

Approfondissement 2 : Défaillances observées lorsque FastMCP était placé derrière AgentCore

Pour l’analyse approfondie 2 : des pannes ont été observées lorsque FastMCP était placé derrière AgentCore ; il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférer des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé. Fournir des outils dotés de schémas restreints et de labels explicites indiquant les effets secondaires. Les hôtes doivent savoir quels appels modifient l’état avant d’approuver automatiquement. Pour l’analyse approfondie 2 : des pannes ont été observées lorsque FastMCP était placé derrière AgentCore ; il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrer les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet de prévenir…

Des factures de surprise peuvent apparaître lorsque le chemin passe des environnements de démonstration aux environnements partagés.

Danger potentiel n°1 : Aucun en-tête HTTP personnalisé ne parvient au conteneur

Pour le danger potentiel n°1 : Aucun en-tête HTTP personnalisé ne parvient au conteneur, il faut définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Authentifiez-vous au niveau du gateway et réautorisez-vous au niveau du plan de données. Un simple jeton porteur ne constitue pas une frontière entre les tenants.

meta = payload.setdefault("params", {}).setdefault("_meta", {})
meta["okta_token"]   = token         # the validated user identity
meta["trino_target"] = routing_hint  # optional routing metadata
class CredentialExtractMiddleware:
    async def __call__(self, scope, receive, send):
        # buffer the whole body
        body = await _read_full_body(receive)
        # pull identity out of params._meta
        meta = json.loads(body).get("params", {}).get("_meta", {})
        token = meta.get("okta_token")        # AgentCore also injects the validated caller token as a header —
        # prefer that when present, fall back to _meta / Authorization otherwise
        token = token or _caller_token_from_headers(scope)        _req_okta_token.set(token)        # replay the buffered body so the MCP app can read it too
        await self.app(scope, _replay(body, receive), send)

Danger potentiel n°2 : AgentCore supprime l’en-tête Accept

Pour le scénario à risque n°2 : AgentCore supprime l’en-tête Accept, définit les entrées, le responsable de l’étape ainsi que les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations ultérieures. Authentifiez au niveau du gateway et réautorisez au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants.

class InjectAcceptMiddleware:
    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            headers = [(k, v) for k, v in scope["headers"] if k != b"accept"]
            headers.append((b"accept", b"application/json, text/event-stream"))
            scope = {**scope, "headers": headers}
        await self.app(scope, receive, send)

Approfondissement 3 : Association des tokens d’accès Okta aux sessions Trino isolées

Pour l’approfondissement 3 : Mappage des tokens d’accès Okta sur des sessions Trino isolées, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé. Authentifiez au niveau du gateway et réautorisez au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants. Pour l’approfondissement 3 : Mappage des tokens d’accès Okta sur des sessions Trino isolées, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues.

Cela se produit lorsque le chemin passe de l’environnement de démonstration à des environnements partagés.

def get_connection():
    token = _req_okta_token.get()
    user  = _jwt_email(token)          # e.g. "alice@company.com"
    return backend.connect(
        host=BACKEND_HOST,
        user=user,                      # identity for attribution/authorization
        auth=JWTAuthentication(token),  # the backend re-validates this itself
        http_scheme="https",
    )

L’autorisation et l’imposition du mode lecture seule relèvent en fin de compte de la couche des données.

Pour que l’autorisation et l’imposition du mode lecture seule relèvent en fin de compte de la couche des données, il faut définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat de chaque appel. Sans cette trace, les boucles du agent de débogage gaspillent des heures.

forbidden = ("INSERT", "UPDATE", "DELETE", "DROP", "CREATE",
             "ALTER", "TRUNCATE", "MERGE", "REPLACE", "GRANT", "REVOKE")
if any(query.strip().upper().startswith(kw) for kw in forbidden):
    raise ValueError("Write operations are not allowed.")

Deux manières de connecter le client

Pour les deux manières de connecter le client, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat de chaque appel. Sans cette trace, déboguer des boucles d’agent prend des heures.

{ "mcpServers": { "data": { "type": "http", "url": "https://<lambda-url>/mcp" } } }
{ "mcpServers": { "data": { "type": "http", "url": "http://localhost:8766/mcp" } } }

Ce que nous avons appris

D’après ce que nous avons appris, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Enregistrez le nom de l’outil, le hash des arguments, la latence et le résultat pour chaque appel. Déboguer sans cette trace gaspille des heures. D’après ce que nous avons appris, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Notez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés.

Conclusion

Pour conclure, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.

Liste de contrôle opérationnelle

Pour la liste de contrôle opérationnelle, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché.

Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès, et refusez les terminaisons partielles silencieuses.

Authentifiez-vous au niveau du gateway et réautorisez-vous au niveau du plan de données. Un token porteur seul ne constitue pas une frontière entre les tenants.

Faites des points de contrôle après les étapes coûteuses. Le mécanisme de reprise ne doit pas facturer à nouveau la même appel LLM lorsque l’opérateur réessaie un nœud ultérieur.

Transférez l’identité du client vers le stockage intermédiaire sans réduire tout le monde à un compte de service.

Ajoutez un test de fumée qui exécute le chemin critique dans l’environnement CI à l’aide de fixtures, et non d’API payantes en temps réel, chaque fois que le budget le permet.

Au préalable de promouvoir l’ensemble des composants, figez les versions, créez une transcription de référence pour le chemin critique, et confirmez les étapes de réversion. Les environnements partagés nécessitent des limites de débit, des vérifications d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité solide à des démonstrations brillantes mais ponctuelles.

Note de déploiement 1 : fixez les images, définites des budgets de requêtes et vérifiez l’isolation des utilisateurs sur un environnement de test avant un déploiement à plus grande échelle.

Note de déploiement 2 : fixez les images, définites des budgets de requêtes et vérifiez l’isolation des utilisateurs sur un environnement de test avant un déploiement à plus grande échelle.

Note de déploiement 3 : fixez les images, définites des budgets de requêtes et vérifiez l’isolation des utilisateurs sur un environnement de test avant un déploiement à plus grande échelle.

Note de déploiement 4 : fixez les images, définites des budgets de requêtes et vérifiez l’isolation des utilisateurs sur un environnement de test avant un déploiement à plus grande échelle.

Note de déploiement 5 : fixez les images, définites des budgets de requêtes et vérifiez l’isolation des utilisateurs sur un environnement de test avant un déploiement à plus grande échelle.

Note de déploiement 6 : fixer les images, définir des budgets de requêtes et vérifier l’isolation des utilisateurs sur un environnement canari avant un déploiement plus large.