Aller au contenu

MCP

Gouverner les serveurs MCP avec RFC 9728 et RFC 8707 : ce qui fonctionne réellement

Par Olivares AI 12 min de lecture

Vous connectez Claude Code à un serveur MCP. Le serveur nécessite une authentification. Votre client envoie une demande ; le serveur répond avec un 401 Unauthorized et un en-tête WWW-Authenticate: Bearer qui porte une URL resource_metadata. Ce qui se passe ensuite est un flux OAuth 2.1 défini par trois RFC et un ensemble d’extensions spécifiques à MCP qui, ensemble, résolvent l’un des problèmes les plus difficiles de la sécurité des agents : s’assurer qu’un jeton obtenu pour communiquer avec ce serveur MCP ne peut pas être rejoué contre celui.

Cet article retrace le flux complet : ce que dit la spécification, ce que les RFC fournissent réellement et où se cachent les pièges de sécurité dans la pratique.

Le flux : de 401 à un jeton lié aux ressources

Le modèle d’autorisation MCP (révision 2025-11-25, conservée inchangée dans la version candidate – 2026-05-21 gelée – pour la spécification finale prévue pour 2026-07-28) est un processus en deux phases. La phase 1 est la détection : le 401 portant le WWW-Authenticate: Bearer resource_metadata="..." indique au client que ce serveur est protégé par OAuth et où trouver ses métadonnées. La phase 2 est l’accès autorisé : consommer les métadonnées, découvrir le serveur d’autorisation, obtenir un jeton lié à ce serveur spécifique et l’utiliser.

Les étapes concrètes :

  1. 401 + WWW-Authenticate : Le serveur MCP rejette une requête non authentifiée. Le paramètre resource_metadata dans le défi pointe vers le document de métadonnées de ressources protégées du serveur.

  2. PRM fetch (RFC 9728) : le client OBTIENT l’URL /.well-known/oauth-protected-resource. La réponse est un document JSON déclarant l’URI canonique de la ressource du serveur, le ou les serveurs d’autorisation qui le protègent et les étendues prises en charge par la ressource. Le client valide que le champ resource du document correspond au serveur qu’il avait l’intention d’atteindre — une incohérence est un signal d’usurpation d’identité et doit être rejetée.

  3. Découverte AS (RFC 8414) : à l’aide de l’URL de l’émetteur du tableau authorization_servers du PRM, le client parcourt les candidats connus (/.well-known/oauth-authorization-server, puis /.well-known/openid-configuration) pour récupérer les métadonnées du serveur d’autorisation. L’émetteur dans le document renvoyé doit être identique en octet à l’émetteur pour lequel il a été récupéré — ni “équivalent après normalisation”, ni “assez proche”. Octet identique.

  4. Acquisition de jeton (RFC 8707) : le client demande un jeton au point de terminaison de jeton du AS, y compris resource=<canonical server URI> dans les demandes d’autorisation et de jeton. Cela lie l’audience du jeton à ce serveur MCP spécifique. Le AS émet un jeton dont l’audience est cette ressource et le client le présente au serveur.

  5. Accès autorisé : Le client utilise le token pour appeler les méthodes d’introspection en lecture seule du serveur MCP (tools/list, resources/list, etc.). Le jeton prouve que le client est autorisé ; l’indicateur de ressource prouve que le jeton a été créé pour ce serveur.

Ce que RFC 9728 fournit réellement

RFC 9728 (Protected Resource Metadata) est un mécanisme de découverte. Il répond : “quel serveur d’autorisation protège cette ressource, et qu’attend-il ?” Il n’authentifie pas le serveur, ne valide pas le jeton et n’applique pas le contrôle d’accès. Ce sont des préoccupations distinctes.

Le document PRM comporte un champ obligatoire (resource) et un ensemble de champs facultatifs, parmi lesquels authorization_servers est le plus important. La spécification MCP renforce le caractère facultatif de la RFC : au moins un serveur d’autorisation est requis. Un document PRM qui n’en répertorie aucun est traité comme une erreur.

Le champ resource est l’URI canonique de la ressource protégée. Le client doit le comparer au serveur qu’il avait l’intention de contacter et rejeter une incompatibilité :

// RFC 9728 §3.3 : la valeur de ressource du PRM DOIT identifier le
// ressource à laquelle le client s'adresse - comparaison de chaîne simple avec
// l'indicateur de ressource auquel ce client lie ses jetons.
if prm.Resource != c.resource {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: protected resource metadata declares resource %q, "+
        "expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}

Cette vérification empêche une classe d’attaque dans laquelle un document PRM malveillant ou mal configuré prétend parler au nom d’une ressource différente. La comparaison n’est pas normalisée : il s’agit d’une correspondance directe de chaîne avec l’URI de ressource canonique que le client a calculé pour l’URL du serveur qui lui a été fournie.

Indicateurs de ressources RFC 8707 : pourquoi la liaison de jetons est importante

Sans indicateurs de ressources, un jeton d’accès obtenu à partir d’un AS peut potentiellement être utilisé sur n’importe quel serveur de ressources protégé par AS. Si vous disposez de deux serveurs MCP (par exemple, un serveur de documentation en lecture seule et un sandbox d’exécution de code) tous deux derrière le même fournisseur d’identité, un jeton obtenu pour l’un pourrait être présenté à l’autre. C’est un problème de confusion pour les sous-ministres.

RFC 8707 résout ce problème en ajoutant un paramètre resource aux demandes d’autorisation et de jeton. Le AS émet un jeton dont l’audience est explicitement cet URI de ressource. Un serveur de ressources correctement validé rejette un jeton dont l’audience ne correspond pas à sa propre identité.

En pratique, la demande de token inclut l’indicateur de ressource aux côtés de la subvention :

form := url.Values{
    "grant_type": {"client_credentials"},
    "resource":   {c.resource}, // RFC 8707 — lier le jeton à l’audience
}

Cela apparaît dans chaque type d’octroi utilisé par le connecteur : informations d’identification du client, échange de code d’autorisation et rotation des jetons d’actualisation. L’indicateur de ressource n’est pas facultatif : il est présent dans chaque demande de jeton, de sorte que chaque jeton est lié à l’audience par construction.

La vérification de l’émetteur à octet identique

La validation la plus critique en matière de sécurité de l’ensemble du flux est également la plus simple à énoncer et la plus facile à se tromper : la valeur de l’émetteur dans le document de métadonnées AS doit être identique en octet à l’émetteur que le client a utilisé pour construire l’URL connue.

Pas égal à la casse. Non équivalent après normalisation du schéma. Ce n’est pas la même chose après avoir supprimé une barre oblique finale. Octet identique. La section 3.3 de RFC 8414 est explicite à ce sujet et la spécification MCP hérite de cette exigence.

// discoverASMetadata : REFUSE un document dont le champ issuer n’est pas
// BYTE-IDENTIQUE au issuer pour lequel il a été récupéré (RFC 8414 §3.3).
// Un document discordant est un signal d’usurpation.
if as.Issuer != issuer {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: AS metadata at %s declares issuer %q, "+
        "expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}

Pourquoi si strict ? Parce qu’un attaquant qui contrôle le DNS ou se trouve sur le chemin réseau peut diffuser un document de métadonnées qui pointe vers son propre point de terminaison de jeton tout en prétendant être un émetteur légitime. Si le client normalisait l’émetteur avant de comparer, https://auth.example.com et https://AUTH.example.com correspondraient — et le document de l’attaquant serait accepté. La comparaison à octets identiques clôt cette opération.

La même discipline s’applique à RFC 9207 (validation de l’émetteur de la réponse d’autorisation). Lorsque le client démarre un flux de code d’autorisation, il enregistre l’émetteur à partir des métadonnées AS validées. Lors de la redirection, le paramètre iss dans la réponse doit correspondre à cette valeur enregistrée – encore une fois, octet par octet, pas de normalisation – avant que le code d’autorisation soit utilisé. Il s’agit de la défense contre la confusion : sans cela, un AS malveillant pourrait intercepter le code et demander au client de l’utiliser au point de terminaison du jeton de l’attaquant.

Identification du client : CIMD remplace DCR

La spécification MCP définit un ordre de priorité pour la manière dont un client s’identifie auprès du serveur d’autorisation :

  1. Identifiants préenregistrés — l’opérateur fournit à l’avance un client_id et un client_secret
  2. CIMD (Documents de métadonnées d’ID client) — le client héberge un document JSON sur une URL HTTPS ; cette URL est le client_id
  3. Enregistrement client dynamique (RFC 7591) — le client s’enregistre au point de terminaison d’enregistrement du AS
  4. Inviter l’utilisateur — ne s’applique pas aux agents sans interface graphique

DCR est obsolète dans la version candidate pour la spécification finale prévue pour 2026-07-28, au profit de CIMD. La raison est opérationnelle : DCR crée un état client persistant au niveau du serveur d’autorisation. Chaque agent qui s’enregistre laisse derrière lui une paire client_id/client_secret que le AS doit stocker, et personne ne les suit ou ne les révoque. Pour une flotte d’agents, il s’agit d’une prolifération incontrôlée des informations d’identification.

CIMD inverse le modèle. Le client héberge un document sur une URL qu’il contrôle. Le AS récupère le document lorsqu’il doit valider le client, le met en cache selon les en-têtes de cache HTTP et ne stocke rien de manière permanente. La rotation des clés est une mise à jour du document. La mise hors service d’un client supprime le document. Aucune inscription orpheline ne s’accumule au AS.

Le compromis : CIMD nécessite que le client exécute un point de terminaison HTTPS. Pour un plan de gouvernance auto-hébergé, c’est naturel : le plan exécute déjà des services HTTPS. Pour un outil CLI sur un ordinateur portable de développeur, c’est moins le cas, c’est pourquoi les informations d’identification préenregistrées restent la première option dans l’ordre de priorité.

Une identité CIMD ne peut pas contenir de secret partagé (l’URL du document est publique : un secret qu’elle contient constituerait une fuite d’informations d’identification). L’authentification client utilise private_key_jwt (RFC 7523) : le client signe un JWT éphémère avec une clé privée dont la contrepartie publique est publiée dans le champ jwks du document CIMD.

La règle du « ne jamais passer par »

Une défense structurelle facile à ignorer : le connecteur n’utilise qu’un jeton qu’il a obtenu lui-même, pour un serveur spécifique, via le flux de découverte décrit ci-dessus. Il n’accepte jamais de jeton d’un tiers et le transmet à un serveur MCP. Il ne lit jamais un jeton d’une requête entrante et ne le transmet pas.

C’est la défense du député confus au niveau protocolaire. Si un client transmettait les jetons qu’il a reçus, un attaquant pourrait présenter un jeton limité à une ressource à faible privilège et demander au client de le transmettre à une ressource à haut privilège (ou vice versa - extraire un jeton à haut privilège en incitant le client à le présenter à un serveur contrôlé par l’attaquant). En obtenant ses propres jetons et en ne touchant jamais ceux de quelqu’un d’autre, le connecteur ne peut pas servir de relais de jetons.

Le transfert de jetons n’est pas seulement déconseillé : il est structurellement impossible. Le client HTTP du connecteur pour les flux OAuth est distinct de tout gestionnaire de requêtes entrantes. Il n’existe aucun chemin de code qui lit un jeton de support à partir d’une requête entrante et l’écrit dans une requête sortante.

SSRF : la défense contre la rereliure DNS

Chaque URL de point de terminaison de métadonnées et de jeton récupérée par le connecteur est protégée par SSRF sur deux couches. La première est une vérification préalable : l’URL doit être HTTPS (sauf bouclage pour le développement local) et ne doit pas être une adresse IP réservée littérale.

La seconde est une vérification de l’heure de numérotation qui ferme le TOCTOU de liaison DNS. Un nom d’hôte qui se résout en une adresse IP publique lors de la vérification préalable au vol pourrait se lier à nouveau à une adresse IP privée au moment où le socket est composé. Le client HTTP du connecteur installe une fonction net.Dialer.Control qui inspecte l’IP résolue concrètement au moment de la connexion et refuse toute adresse réservée. Il s’agit de la vérification faisant autorité – la vérification avant le vol est un rejet rapide dans les cas évidents, mais la vérification de l’heure de numérotation est celle qui compte.

Augmentation du périmètre

Un serveur MCP peut répondre à une requête avec WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". La spécification (SEP-835/SEP-2350) définit comment le client gère cela : calcule l’union des étendues qu’il a précédemment demandées et des étendues que le serveur vient de contester, puis ré-acquiert un jeton avec cet ensemble étendu. L’union conserve les autorisations précédemment accordées et ajoute les nouvelles. L’intensification se produit une seule fois : une deuxième contestation de portée insuffisante sur la même requête n’est pas réessayée, pour éviter des boucles infinies.

Le serveur est autorisé à être sans état dans son défi : il nomme uniquement les étendues dont l’opération en cours a besoin, et non l’ensemble complet que le client aurait pu demander auparavant. L’accumulation côté client permet que cela fonctionne sans que le serveur ne suive l’historique de la portée par client.

Ce que cela signifie en pratique

Le flux OAuth pour les serveurs MCP est bien spécifié et, lorsqu’il est mis en œuvre de manière stricte, il s’attaque aux véritables surfaces d’attaque : relecture de jetons sur les serveurs, emprunt d’identité des métadonnées, SSRF de liaison DNS et prolifération incontrôlée des informations d’identification des clients. La vérification de l’octet identique de l’émetteur, l’indicateur de ressource dans chaque demande de jeton et la règle structurelle de non-passthrough constituent les principales défenses. Ce ne sont pas des fonctionnalités que vous pouvez implémenter à moitié – chacune est une vérification difficile qui échoue.

Le problème le plus difficile est opérationnel. Les équipes déployant des serveurs MCP doivent :

  • Publiez un document PRM valide sur /.well-known/oauth-protected-resource avec un champ resource qui correspond à l’URI canonique de leur serveur.
  • Utilisez un AS qui prend en charge les indicateurs de ressources — de nombreux fournisseurs d’identité ne le font toujours pas, ou traitent le paramètre resource comme consultatif plutôt que comme contraignant pour l’audience.
  • Passez de DCR à CIMD avant que la dépréciation ne devienne une suppression, ou pré-enregistrez explicitement les informations d’identification
  • Épinglez les informations d’identification à un émetteur pour empêcher la réutilisation silencieuse entre émetteurs lorsque la topologie AS change

La documentation du connecteur MCP couvre la configuration opérationnelle et le modèle de sécurité décrit comment le jeton lié aux ressources s’intègre dans la carte d’accès plus large.

Articles liés

Questions fréquentes

RFC 9728 garantit-il que le serveur MCP avec lequel je parle est légitime ?

Non. RFC 9728 permet au client de découvrir quel serveur d'autorisation protège une ressource et quelles étendues il requiert, mais il s'agit de métadonnées sur la ressource, et non d'une preuve de l'identité de la ressource. Un certificat TLS prouve le nom d'hôte du serveur ; PRM vous indique comment vous y authentifier. Les deux sont complémentaires : PRM sans vérification TLS est une découverte sur un hôte non vérifié, et TLS sans PRM laisse le client deviner comment obtenir un jeton.

Pourquoi l'enregistrement dynamique des clients est-il obsolète dans la spécification MCP s'il fonctionne toujours ?

DCR crée un état client persistant sur le serveur d'autorisation : un client_id et un client_secret que le AS doit stocker et gérer. Pour une flotte d'agents sans tête, cela devient un problème de prolifération incontrôlée des informations d'identification : chaque agent s'enregistre lui-même, et personne ne suit ou ne fait tourner ces enregistrements. CIMD (Documents de métadonnées d'ID client) remplace cela par un document hébergé que le client contrôle et que le AS récupère à la demande — pas d'état persistant au niveau du AS, pas d'enregistrements orphelins et la rotation des clés est une mise à jour du document plutôt qu'un réenregistrement.

Découvrez ce que vos agents peuvent atteindre

Olivares AI est la plateforme ouverte et auto-hébergée pour votre parc informatique d'IA. Déployez-la sur votre propre infrastructure et obtenez la cartographie des accès que réclament vos équipes de sécurité et de plateforme.