OAuth pour un serveur MCP : comment il authentifie un agent, et ce qui casse
Un serveur MCP distant authentifie un agent comme un serveur de ressources OAuth 2.1. Il répond à un appel sans token par un 401 et un en-tête WWW-Authenticate, le client suit ce lien jusqu'aux métadonnées du serveur, déroule un flux de code d'autorisation avec PKCE, et envoie le token en en-tête bearer sur chaque requête. Voici chaque étape, exécutée en direct aujourd'hui.
L'autorisation est optionnelle dans MCP, et seulement pour les transports HTTP : un serveur stdio local lit ses identifiants dans l'environnement. Tout ce qui suit vient de la révision courante de la spécification, 2026-07-28, et chaque réponse est un appel réel à https://api.plugkit.co/mcp passé le 16 octobre 2026.
La poignée de main, en quatre réponses
Étape 1. Appeler sans token et lire le défi. Le serveur doit renvoyer un 401 avec un en-tête WWW-Authenticate qui pointe vers ses métadonnées.
curl -i -X POST https://api.plugkit.co/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://api.plugkit.co/.well-known/oauth-protected-resource", scope="mcp:read mcp:write"
{"error":"Missing credentials. Send `Authorization: Bearer <api key or session token>`.","type":"authentication_error","code":"unauthorized"}
L'indice scope est un SHOULD dans la spec, pas un MUST, et c'est la différence entre un client qui demande les bonnes permissions du premier coup et un client qui devine. Les clients doivent traiter les scopes du défi comme faisant autorité pour l'opération.
Étape 2. Récupérer les métadonnées de la ressource protégée. C'est la RFC 9728, et c'est un MUST pour tout serveur MCP.
{"resource":"https://api.plugkit.co/mcp",
"authorization_servers":["https://api.plugkit.co"],
"scopes_supported":["mcp:read","mcp:write"],
"bearer_methods_supported":["header"],
"resource_documentation":"https://docs.plugkit.co"}
Étape 3. Récupérer les métadonnées du serveur d'autorisation. RFC 8414, ou OpenID Connect Discovery ; le serveur doit en proposer au moins une, et le client doit gérer les deux.
{"issuer":"https://api.plugkit.co",
"authorization_endpoint":"https://api.plugkit.co/oauth/authorize",
"token_endpoint":"https://api.plugkit.co/oauth/token",
"registration_endpoint":"https://api.plugkit.co/oauth/register",
"revocation_endpoint":"https://api.plugkit.co/oauth/revoke",
"code_challenge_methods_supported":["S256"],
"token_endpoint_auth_methods_supported":["none"],
"client_id_metadata_document_supported":true,
"authorization_response_iss_parameter_supported":true}
Étape 4. Dérouler le flux de code et rappeler avec le token. Code d'autorisation avec PKCE, le paramètre resource sur les deux requêtes, puis Authorization: Bearer <token> sur absolument chaque requête HTTP, y compris celles d'une même session.
Authentification d'un serveur MCP : d'où vient le client ID
C'est la partie qui a changé, et celle que la plupart des tutoriels se trompent encore. La révision courante liste trois mécanismes et les classe.
| Mécanisme | Statut dans la révision 2026-07-28 | Ce que le client envoie comme client_id |
|---|---|---|
| Client ID Metadata Documents | SHOULD support | Une URL HTTPS que le serveur va chercher pour y lire les métadonnées du client |
| Pré-enregistrement | Autorisé | Un client ID qui t'a été remis hors ligne |
| Dynamic Client Registration (RFC 7591) | MAY support, déprécié | Un client ID issu de POST /register |
L'enregistrement dynamique était un SHOULD dans la révision 2025-06-18. Il est désormais explicitement « deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents ». CIMD le remplace par quelque chose de plus simple : le client utilise une URL comme identifiant, le serveur d'autorisation va chercher cette URL, lit les métadonnées et valide les redirect URI. Pas d'appel d'enregistrement, pas de fiche client stockée par client.
Le serveur PlugKit ci-dessus annonce les deux, client_id_metadata_document_supported: true et un registration_endpoint actif, ce qui est la bonne posture le temps que les clients rattrapent.
OAuth 2.1 pour MCP : PKCE, le paramètre resource et la liaison d'audience
Trois exigences font le travail de sécurité, et les trois sont des MUST pour les clients.
- PKCE. Obligatoire sur tous les clients.
S256est la méthode publiée par le serveur ci-dessus. Il empêche qu'un code d'autorisation intercepté soit échangé par un autre client que celui qui a lancé le flux. - Le paramètre
resource. RFC 8707, sur la requête d'autorisation comme sur la requête de token, portant l'URI canonique du serveur MCP :&resource=https%3A%2F%2Fapi.plugkit.co%2Fmcp. Les clients doivent l'envoyer, que le serveur le prenne en charge ou non. - La validation de l'audience. Le serveur doit vérifier que le token a bien été émis pour lui, et doit rejeter tout le reste. La spec est catégorique : un serveur MCP « MUST NOT accept or transit any other tokens ».
Cette dernière a un nom. Si un serveur prend un token destiné à quelqu'un d'autre et le transmet en amont, tu obtiens le problème du député confus : l'API en aval fait confiance à l'appel parce qu'il arrive du serveur. Le passthrough de token est interdit, et c'est pourquoi un serveur qui appelle une API en amont doit détenir son propre token séparé pour elle. C'est la moitié « serveur vers plateforme » du problème, et elle a ses propres pannes, traitées dans les tokens d'accès des réseaux sociaux.
Ce qui casse
Cinq pannes, dans l'ordre où tu as le plus de chances de les croiser.
| Symptôme | Statut | Cause | Solution |
|---|---|---|---|
| Le client ne démarre jamais le flux | 401 | Pas d'en-tête WWW-Authenticate, ou pas de métadonnées de ressource |
Implémente la RFC 9728, renvoie l'en-tête sur chaque 401 |
| Appel d'outil refusé, le token a l'air bon | 403 | Le token manque d'un scope dont l'opération a besoin | Renvoie error="insufficient_scope" avec scope, et laisse le client élever ses scopes |
| Boucle de demandes de consentement dans le navigateur | 401 | Refresh token expiré ou révoqué, le client relance le flux à l'aveugle | Limite le nombre de tentatives, traite un échec répété comme définitif |
| Token rejeté par un deuxième serveur | 401 | Token envoyé à un serveur pour lequel il n'a pas été émis | Envoie le paramètre resource, valide l'audience |
| Le flux se termine contre le mauvais serveur | aucun | Attaque de confusion de serveurs, iss non vérifié |
Note l'émetteur avant de rediriger, compare au retour |
Celle des scopes mérite un détail, parce qu'elle est nouvelle et facile à rater. Quand un token est valide mais trop étroit, le serveur doit répondre 403 avec error="insufficient_scope" et les scopes dont l'opération a besoin. Le client réautorise alors avec l'union de ce qu'il avait déjà et de ce qui vient d'être demandé, pas seulement le nouveau scope, sinon il perd les permissions qu'il utilisait avant. Les serveurs devraient envoyer tous les scopes nécessaires à l'opération dans un seul défi, au lieu de les distiller un par aller-retour.
La vérification de iss est la plus discrète des cinq, parce que rien ne casse visiblement. Le client doit noter l'émetteur depuis les métadonnées validées avant d'ouvrir le navigateur, puis comparer le iss qui revient sur le callback, sans aucune normalisation : pas de casse harmonisée, pas de slash final, pas de nettoyage du percent-encoding. Le serveur ci-dessus pose authorization_response_iss_parameter_supported: true, donc un iss absent de la réponse doit entraîner un rejet immédiat.
Auth d'un serveur MCP distant : OAuth ou clé d'API ?
Les deux marchent, et l'arbitrage porte sur les scopes et la révocation, pas sur du théâtre de sécurité.
| OAuth 2.1 | Clé d'API | |
|---|---|---|
| Granularité | Des scopes, la lecture seule est possible | Accès complet, aucun scope |
| Qui détient le secret | Le client, renouvelable | Toi, dans un fichier de config ou une variable d'environnement |
| Révocation | Par application connectée, par l'utilisateur | Fais tourner la clé, ça casse tous les consommateurs |
| Adapté à | Clients interactifs, tiers | Scripts, cron, ton propre backend |
Sur le serveur ci-dessus, les deux scopes se séparent proprement : mcp:read couvre les profils, les comptes, les posts, les conversations et les analytics ; mcp:write ajoute la publication, l'envoi de messages, la réponse aux commentaires et la connexion de comptes. Une clé d'API porte un accès complet et aucun scope, ce qui est la raison honnête de préférer OAuth pour tout ce qu'un modèle pilote en interactif. Fais tourner un agent sur mcp:read pendant une semaine avant de le laisser écrire, comme dans Claude comme gestionnaire de tes réseaux sociaux.
Le même serveur est accessible depuis Claude, ChatGPT, Claude Code, Cursor et VS Code, et coûte 49 $ par mois pour 5 comptes connectés, résiliable à tout moment, avec 7 jours gratuits sans carte bancaire. Savoir si un serveur MCP est même la bonne forme pour ton intégration est une autre question, traitée dans MCP ou API REST pour ton agent ; pour un exemple concret dans un client, vois le serveur MCP Instagram.
Texte normatif complet : la spécification d'autorisation MCP, lue le 16 octobre 2026.
FAQ
Un serveur MCP a-t-il besoin d'OAuth ?
Seulement s'il est distant et que tu veux une autorisation par utilisateur. L'autorisation est optionnelle dans la spec, et les serveurs stdio locaux ne devraient pas l'utiliser du tout, mais prendre leurs identifiants dans l'environnement. Un serveur HTTP qui sert plusieurs utilisateurs devrait l'implémenter, parce qu'une clé d'API donne le même accès complet à quiconque la détient.
Quelle version d'OAuth MCP utilise-t-il ?
OAuth 2.1, avec PKCE obligatoire sur tous les clients, les métadonnées de ressource protégée (RFC 9728) obligatoires sur tous les serveurs, et le paramètre resource de la RFC 8707 obligatoire sur les requêtes d'autorisation et de token. La révision courante est 2026-07-28, qui ajoute aussi l'identification de l'émetteur de la RFC 9207.
L'enregistrement dynamique de client est-il encore obligatoire pour MCP ?
Non. Il est passé de SHOULD dans la révision 2025-06-18 à MAY dans la courante, et la spec le qualifie de déprécié, conservé pour les serveurs qui ne gèrent pas les Client ID Metadata Documents. CIMD est la voie recommandée : le client utilise une URL HTTPS comme client ID et le serveur d'autorisation va y lire les métadonnées.
Pourquoi mon client MCP me redemande-t-il sans cesse de me connecter ?
En général un refresh token expiré ou révoqué, avec un client qui relance le flux au lieu de s'arrêter. La spec dit aux clients de limiter les tentatives et de traiter un échec répété comme définitif. Vérifie si l'utilisateur a révoqué l'app, et si ton serveur d'autorisation fait tourner les refresh tokens, ce qu'il doit faire pour les clients publics.
Que veut dire un 403 renvoyé par un serveur MCP ?
Le token est valide mais il manque un scope dont l'opération a besoin. Le serveur devrait renvoyer error="insufficient_scope" avec les scopes requis, et le client devrait réautoriser en demandant ceux-là plus tout ce qu'il avait déjà. Un 401 veut dire identifiants manquants ou invalides, et un 400 veut dire que la requête d'autorisation elle-même était malformée.
Un plug pour chaque plateforme que ton agent touche
49 $ par mois pour 5 comptes connectés, résiliable à tout moment. Toutes les fonctionnalités dans tous les forfaits.
Récupère ta clé API →