OAuth pour un serveur MCP : Claude et ChatGPT se connectent sans jeton

OAuth

Il y a quelques semaines, j'ai expliqué pourquoi les agents IA ont besoin de jetons personnels. La configuration décrite à la fin de cet article fonctionne bien dans Claude Code : créez un jeton, lancez une commande, et votre agent peut lire les rapports de bug.

Puis des gens ont essayé d'ajouter BugDump à Claude sur le web, et ça n'a pas marché.

Un connecteur personnalisé Claude n'a aucun endroit où coller un jeton. ChatGPT non plus. VS Code accepte un en-tête si vous modifiez son fichier de configuration, mais son parcours « Add Server » n'en demande pas. Les clients MCP qu'utilisent la plupart des gens attendent une URL, et quand le serveur répond 401, ils s'attendent à ce que l'utilisateur se connecte.

J'ai donc ajouté la connexion par OAuth à mon serveur MCP. Le code a été la partie la plus rapide. Voici à quoi ça ressemble pour la personne qui se connecte, et les quatre décisions qui ont pris le plus de temps.

Comment un client MCP se connecte

Vous copiez https://api.bugdump.com/api/mcp et l'ajoutez à Claude comme connecteur personnalisé, ou vous cliquez sur Ajouter à Claude sur la page MCP de votre projet, qui fait la copie pour vous. Claude ouvre une page BugDump dans votre navigateur. Vous vous connectez, choisissez si l'app peut modifier des choses ou seulement les lire, et cliquez sur Autoriser.

Page de consentement BugDump avec la question Autoriser Claude à utiliser votre compte BugDump, l'option Lecture et écriture sélectionnée, Lecture seule comme autre option, et les boutons Autoriser et Annuler

Sous le capot, c'est le flux que décrit la spécification MCP. La première requête revient en 401 avec un en-tête WWW-Authenticate qui pointe vers un petit document JSON. Ce document nomme le serveur qui gère la connexion. Claude récupère les métadonnées de ce serveur, vous envoie vers la page de connexion et échange le résultat contre un jeton. Rien de tout cela n'est propre à Claude. Un client qui suit la spécification n'a besoin que de l'URL pour trouver la page de connexion.

Une seule connexion pour tous vos projets

Dans BugDump, les jetons personnels sont liés à un seul projet. C'est un modèle net. Un jeton pour Acme Store ne voit pas Acme Admin, et personne n'a à se demander dans quel projet l'agent travaille.

Je comptais le garder aussi pour la connexion par OAuth, avec une connexion par projet, peut-être une URL par projet. Puis j'ai essayé d'en ajouter une deuxième. Claude refuse d'ajouter deux fois la même URL de connecteur, et une URL différente par projet signifie un connecteur distinct à configurer et à suivre pour chacun. Quelqu'un avec trois projets se retrouverait avec trois connecteurs, et la plupart des gens s'arrêteraient après le premier.

Une connexion par OAuth vous appartient donc à vous, pas à un projet. Elle atteint tous les projets dont vous êtes membre, et tous les autres outils prennent un argument supplémentaire, project : le slug du projet, qu'on peut omettre si vous n'appartenez qu'à un seul projet. Un nouvel outil, list_projects, indique à l'assistant ce qu'il peut atteindre, y compris la clé de chaque projet, pour qu'il sache que AS-1 se trouve dans Acme Store.

Ce que je préfère, c'est ce qui se passe quand l'assistant oublie de préciser de quel projet il parle. Le serveur ne choisit pas à sa place. Si vous appartenez à plusieurs projets, l'appel revient avec ceci :

JSON
{
  "error": "MCP_PROJECT_REQUIRED",
  "message": "This account belongs to several projects. Pass `project` with the slug of the one the user means.",
  "projects": [
    { "slug": "acme-admin", "key": "AA", "name": "Acme Admin", "role": "OWNER" },
    { "slug": "acme-store", "key": "AS", "name": "Acme Store", "role": "OWNER" }
  ]
}

Il revient comme résultat de l'outil lui-même, et non comme une erreur de protocole, si bien que le modèle le lit et réessaie avec le bon slug. Ça ne devrait pas arriver souvent : les instructions du serveur demandent à l'assistant d'appeler list_projects avant toute chose.

L'appartenance au projet est vérifiée à chaque appel. Si quelqu'un quitte un projet, son assistant y perd l'accès dès la requête suivante, et il n'y a rien à nettoyer.

Uniquement des clients connus : Client ID Metadata Documents

Dans l'OAuth classique, une app est enregistrée auprès du serveur avant de pouvoir demander un accès à qui que ce soit, soit à la main, soit via Dynamic Client Registration. Avec l'approche plus récente que recommande la spécification MCP, Client ID Metadata Documents, une app s'identifie plutôt par une URL qu'elle contrôle. Celle de Claude est un fichier JSON sur claude.ai qui, entre autres, liste les adresses où Claude a le droit de recevoir le résultat de la connexion.

C'est une bonne conception. Mais à elle seule, elle signifie que n'importe qui capable d'héberger un fichier JSON peut apparaître sur votre page de consentement sous le nom de son choix.

BugDump accepte donc quatre apps : Claude, Claude Code et VS Code par l'URL exacte de leur document, et ChatGPT par un motif fixe sur chatgpt.com, parce qu'il publie un document par connexion. La page de consentement affiche le nom de l'app et l'hôte où se trouve son document (« Demandé par claude.ai »), ce que l'app ne peut pas falsifier. Dynamic Client Registration est désactivé. Cursor l'utilise, donc Cursor continue de passer par un jeton personnel, comme les scripts et les jobs de CI. C'est de toute façon ce que les jetons font bien.

Vérifier le jeton, le consentement et le compte à chaque requête

Un jeton d'accès dure une heure. Ensuite, l'app échange discrètement un jeton de rafraîchissement contre un nouveau, et c'est ainsi qu'une connexion reste active des semaines sans vous redemander de vous connecter. Le hic, c'est que l'étape de rafraîchissement ne s'arrête pas pour demander si vous voulez toujours que l'app soit connectée, ni si votre compte est toujours actif.

Mon endpoint MCP pose donc la question à chaque fois. À chaque requête, il vérifie que le jeton d'accès existe et n'a pas expiré, que votre consentement pour cette app existe toujours, et que votre compte est actif et n'est pas un compte invité. Déconnecter une app supprime d'un coup son consentement et tous les jetons qu'elle détient, et son tout prochain appel échoue avec un 401 qui la renvoie vers la page de connexion. Changer ou réinitialiser votre mot de passe déconnecte toutes les apps à la fois, et c'est ce que vous voulez si vous le changez parce que quelque chose a fuité.

Applications connectées dans la sécurité du compte BugDump, avec VS Code en Lecture seule, Claude Code et Claude en Lecture et écriture, chacun avec un bouton Déconnecter

Les jetons eux-mêmes sont des chaînes aléatoires stockées sous forme de hash, comme les jetons personnels. Il n'y a pas de clés de signature à faire tourner, et révoquer un jeton revient à supprimer une ligne.

L'assistant agit toujours en votre nom

Tout le reste est resté identique, volontairement. Une app connectée par OAuth agit en votre nom et voit ce que vous voyez. Chaque modification qu'elle fait apparaît dans l'historique et les commentaires de la tâche sous votre nom, avec la mention via MCP, et le journal d'audit enregistre quelle app l'a faite. Choisissez Lecture seule et les outils d'écriture ne sont même pas enregistrés : le modèle ne voit jamais move_task, il ne peut donc pas essayer de l'appeler.

La tâche BugDump Checkout button does nothing on Safari avec un commentaire que Claude a publié sous le nom d'Alex Rivera, marqué via MCP

C'est Claude sur un bug de paiement dans Safari. Il a lu la tâche, laissé une note sur l'endroit où chercher en premier, l'a déplacée dans En cours et l'a assignée. Quiconque lit la tâche plus tard voit clairement quelle partie vient de l'assistant.

Ce qui reste perfectible

  • ChatGPT a besoin du mode développeur pour ajouter un serveur MCP personnalisé, et le mode développeur exige un abonnement ChatGPT payant.

  • Changer l'accès oblige à se reconnecter. Pour donner l'accès en écriture à une app en lecture seule, vous la déconnectez puis la reconnectez. Je préfère que ce choix soit délibéré plutôt que confié à un interrupteur qu'on oublie, mais c'est une étape de plus.

  • La mention ne nomme pas encore l'app. Un commentaire indique « via MCP », pas « via Claude ». Chaque commentaire et chaque entrée d'historique enregistre déjà quelle app l'a créé ; la page de la tâche ne l'affiche simplement pas encore.

Connecter BugDump à Claude, ChatGPT ou VS Code

Ouvrez la page Serveur MCP de n'importe quel projet et cliquez sur Ajouter à Claude, ou copiez l'URL dans ChatGPT ou VS Code. Les étapes pour chaque app sont dans Connecter Claude, ChatGPT ou VS Code à BugDump. Si vous êtes sur Cursor ou que vous branchez un script, les jetons personnels fonctionnent toujours exactement comme avant : voir Utiliser BugDump avec Claude Code (MCP).

Pour aller plus loin