Aller au contenu
IA — MCP — Agent IA — Agentic

7 pannes de serveur MCP qui ressemblent à des bugs OAuth

invalid_client intermittent, clients déconnectés, 500 sur /authorize, 403 Cloud Run : 7 pannes de serveur MCP en production et leur vraie cause.

7 pannes de serveur MCP qui ressemblent à des bugs OAuth

Serveur MCP en production : ces pannes qui ressemblent à des bugs d'authentification

Faire fonctionner un serveur MCP sur son poste prend une heure. Le faire tenir en production, derrière OAuth, sur un runtime autoscalé comme Cloud Run, prend nettement plus longtemps. Et la plupart des incidents que j'ai rencontrés partagent une signature trompeuse : ils se présentent comme des erreurs d'authentification alors que la cause est opérationnelle. Résultat, on débogue la mauvaise couche. Voici les pannes que j'ai vécues, avec leur symptôme exact et leur vraie cause.

Panne 1 : erreurs invalid_client intermittentes et reconnexion après chaque déploiement

Symptôme : un utilisateur se connecte, son client fonctionne, puis échoue au rafraîchissement du token avec un invalid_client ou un 401 Unauthorized. Une minute plus tard, cela remarche. Et après chaque déploiement, tous les clients doivent se reconnecter.

Cause : le stockage par défaut des clients OAuth de la bibliothèque (ici FastMCP) était un fichier sur le disque local. Sur Cloud Run, ce disque est éphémère et propre à chaque instance : selon l'instance qui recevait la requête, le client enregistré existait ou non, et un déploiement effaçait tout.

Correctif : un store partagé, externe à l'instance (Firestore, Redis, base SQL), avec les tokens amont chiffrés. Attention : le passage du protocole en mode stateless avec la révision 2026-07-28 ne concerne pas cet état-là. La spécification a cessé de gérer les sessions de transport ; vos enregistrements OAuth n'en ont jamais fait partie, et un serveur "stateless" a toujours besoin d'un store partagé pour eux.

Panne 2 : tous les clients MCP déconnectés d'un coup après une rotation de secret

Symptôme : un matin, tous les clients connectés reçoivent invalid_token et doivent se réauthentifier. Aucun déploiement de code la veille.

Cause : la bibliothèque dérivait la clé de signature des tokens à partir du secret du client OAuth amont. Ce secret avait été tourné. Aucun lien visible entre la cause (une rotation de secret, considérée comme une bonne pratique) et l'effet (une déconnexion générale).

Correctif : une clé de signature dédiée et épinglée dans Secret Manager, indépendante de tout autre secret. Et un déchiffrement des tokens stockés qui, en cas d'échec, traite l'entrée comme absente (nouvelle authentification) plutôt que de lever une exception qui produit un 500.

Panne 3 : la collection de stockage OAuth est vide

Symptôme : pour diagnostiquer la panne 1, j'inspecte les enregistrements dans la collection Firestore configurée. Elle est vide, alors que les clients fonctionnent.

Cause : la bibliothèque codait en dur ses noms de collection tout en semblant honorer le paramètre de configuration. L'état existait, ailleurs.

Correctif : vérifier l'emplacement réellement utilisé, pas celui configuré. Une lecture du code source de la bibliothèque aurait évité une demi-journée.

Panne 4 : 500 Internal Server Error sur /authorize pour certains clients seulement

Symptôme : la plupart des clients MCP se connectent ; deux d'entre eux reçoivent une erreur 500 à l'autorisation. Les logs montrent une erreur de clé de stockage (lacks a collection id, clé illégale).

Cause : ces clients utilisaient déjà les Client ID Metadata Documents (CIMD) : leur client_id est une URL (https://app.example.com/oauth/client.json). Le store clé-valeur interprétait les / de la clé comme des séparateurs de chemin. Et l'erreur sortait en 500 plutôt qu'en erreur OAuth, car le chemin de gestion d'erreur relisait le même client et échouait à nouveau.

Correctif : assainir les clés de stockage (hash ou encodage). Avec la dépréciation de Dynamic Client Registration dans la révision 2026-07-28, un client_id en forme d'URL devient la norme, pas l'exception.

Panne 5 : correctif OAuth déployé, mais le client MCP renvoie toujours la même erreur

Symptôme : je corrige une erreur invalid_scope: Client was not registered with scope email, je déploie, le client renvoie exactement la même erreur.

Cause : le client avait mis en cache un client_id obtenu avant le correctif, et les droits accordés étaient stockés côté serveur pour ce client_id. Les nouveaux enregistrements fonctionnaient ; l'ancien ne pouvait plus fonctionner. C'est la panne qui m'a coûté le plus de temps sur l'ensemble du projet.

Correctif : vérifier côté serveur et côté client séparément. Enregistrer un client neuf exactement comme le fait le CLI (curl -X POST .../register) ; si cela fonctionne, vider le cache OAuth du client (codex mcp logout, fichiers ~/.copilot/mcp-oauth-config/, "Clear authentication" dans le menu /mcp de Claude Code). La révision 2026-07-28 va dans le même sens en imposant aux clients d'indexer leurs identifiants par issuer et de se réenregistrer si celui-ci change. Les clients qui ne le font pas vous présentent le problème comme un bug de votre serveur.

Panne 6 : la révision Cloud Run ne démarre pas (Permission denied on secret)

Symptôme : le déploiement échoue, la nouvelle révision ne reçoit jamais de trafic. Rien dans les logs applicatifs. Seuls les logs de la révision indiquent Permission denied on secret.

Cause : un nouveau secret avait été ajouté, mais le compte de service d'exécution n'avait pas le rôle roles/secretmanager.secretAccessor dessus. Une policy vide sur un seul secret suffit à empêcher le démarrage.

Correctif : accorder l'accès à chaque nouveau secret dans le même changement qui l'introduit (idéalement en Terraform), et lire les logs de révision avant les logs applicatifs.

Panne 7 : 403 en HTML sur /.well-known/oauth-authorization-server après un redéploiement

Symptôme : du jour au lendemain, plus aucun client ne peut se connecter. Les endpoints de découverte et /register répondent 403 avec une page HTML Google.

Cause : un redéploiement sans --allow-unauthenticated a retiré le binding IAM allUsers → roles/run.invoker. Cloud Run bloque alors les requêtes avant votre code, alors que votre serveur MCP gère lui-même l'authentification OAuth.

Correctif : réaffirmer ce binding dans la CI à chaque déploiement, et faire échouer le job si un 403 persiste sur /.well-known/oauth-protected-resource.

Deux fuites qui ne cassent rien

Ces deux-là ne provoquent aucune panne, ce qui les rend plus dangereuses :

  • Les codes OAuth dans les access logs. Un serveur qui journalise la ligne de requête complète écrit code= et state= dans Cloud Logging. Ce sont des identifiants à usage unique, mais ils n'ont rien à faire là. Masquez-les.
  • Le niveau de log livré. Un LOG_LEVEL=DEBUG oublié dans un Dockerfile a envoyé des documents entiers dans les logs cloud. Vérifiez la valeur réellement déployée, pas celle de votre .env.

Le tableau de diagnostic

SymptômeCause probable
invalid_client intermittent, reconnexion après chaque déploiementÉtat OAuth sur disque local, runtime autoscalé
Tous les clients en invalid_token d'un coupClé de signature changée via un secret amont tourné
Collection de stockage videNom de collection codé en dur par la bibliothèque
500 à l'autorisation pour certains clientsclient_id en URL (CIMD) utilisé comme clé de stockage
Correctif déployé, client inchangéEnregistrement client en cache
Révision qui ne démarre pas, Permission denied on secretSecret sans droit d'accès pour le compte de service
403 HTML sur /.well-known/*Binding IAM allUsers perdu au redéploiement

Conclusion

Le protocole MCP est la partie facile. Ce qui casse en production, c'est tout ce qui l'entoure : le stockage, les secrets, l'IAM, les logs, les caches des clients. Mon réflexe aujourd'hui, face à une "erreur d'authentification" sur un serveur MCP, est de vérifier d'abord trois choses : où vit l'état OAuth, quelle clé signe les tokens, et si le client parle avec un enregistrement antérieur à mon dernier déploiement. Dans la majorité des cas, la réponse est là, et pas dans le code OAuth.

Dernier