OAuth 2.0 pour Serveurs MCP
Pourquoi OAuth dans MCP ?
Quand un serveur MCP accède à des données personnelles — emails, calendrier, dépôts GitHub, tickets Linear — il doit prouver qu’il agit au nom de l’utilisateur. Un simple token statique ne suffit pas : il faut un mécanisme de consentement explicite, de droits limités (scopes), et de renouvellement sécurisé. C’est exactement ce que propose OAuth 2.0.
Les trois niveaux d’authentification MCP (rappel)
- Aucune auth : URL publique, pas de données sensibles
- Token statique : clé API générée manuellement (GitHub Personal Access Token)
- OAuth 2.0 : flux de consentement complet avec scopes et refresh tokens
Le flux OAuth 2.0 dans MCP
Quand un utilisateur connecte un serveur MCP avec OAuth dans Le Chat :
1. L utilisateur clique "Connecter" dans Le Chat
↓
2. Le Chat redirige vers la page d autorisation du service
(ex: GitHub, Google, Linear)
↓
3. L utilisateur accorde les permissions demandées (scopes)
↓
4. Le service renvoie un code d autorisation au callback
↓
5. Le client échange le code contre un access token + refresh token
↓
6. L access token est utilisé pour chaque appel MCP
↓
7. Quand l access token expire, le refresh token en obtient un nouveau
Les tokens en jeu
- Access token : utilisé à chaque requête, durée de vie courte (1h typiquement)
- Refresh token : permet d’obtenir un nouvel access token sans reconsentement
- Authorization code : temporaire, échangé une seule fois contre les tokens
Configurer OAuth côté client Python
Le SDK Mistral fournit un helper build_oauth_params pour simplifier la configuration OAuth :
from mistralai.extra.mcp.auth import build_oauth_params
# Construire les paramètres OAuth
oauth_params = build_oauth_params(
client_id="votre_client_id",
client_secret="votre_client_secret",
authorize_url="https://github.com/login/oauth/authorize",
token_url="https://github.com/login/oauth/access_token",
scopes=["repo", "issues"],
redirect_uri="http://localhost:3000/callback",
)
Le helper gère automatiquement :
- Le lancement d’un serveur callback local pour recevoir le code d’autorisation
- L’échange du code contre les tokens
- Le stockage et le renouvellement des tokens
Tokens et scopes : le principe du moindre privilège
Les scopes définissent ce que le serveur MCP peut faire au nom de l’utilisateur. Demandez toujours le minimum nécessaire :
# Mauvais : trop de permissions
scopes=["repo", "admin:org", "delete_repo", "user"]
# Bon : juste ce qu il faut pour créer des issues
scopes=["repo:issues", "read:user"]
Chaque service a ses propres scopes. Consultez la documentation OAuth du service cible pour choisir les bons.
Sécurité des tokens
Stockage
Les tokens doivent être stockés de manière sécurisée :
import os
import json
from pathlib import Path
TOKEN_FILE = Path.home() / ".mcp" / "tokens.json"
def save_tokens(service: str, access_token: str, refresh_token: str):
"""Sauvegarde les tokens de manière sécurisée."""
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
tokens = {}
if TOKEN_FILE.exists():
tokens = json.loads(TOKEN_FILE.read_text())
tokens[service] = {
"access_token": access_token,
"refresh_token": refresh_token,
}
TOKEN_FILE.write_text(json.dumps(tokens))
# Restreindre les permissions du fichier
os.chmod(TOKEN_FILE, 0o600)
Renouvellement automatique
Quand l’access token expire, le refresh token permet d’en obtenir un nouveau sans intervention de l’utilisateur :
import httpx
async def refresh_access_token(
token_url: str,
client_id: str,
client_secret: str,
refresh_token: str,
) -> dict:
"""Renouvelle l access token via le refresh token."""
async with httpx.AsyncClient() as http:
response = await http.post(
token_url,
data={
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": client_id,
"client_secret": client_secret,
},
)
response.raise_for_status()
return response.json()
Serveurs MCP avec authentification par token
Pour les cas plus simples (API key), pas besoin d’OAuth complet. Un header HTTP suffit :
from mistralai.extra.mcp.sse import MCPClientSSE, SSEServerParams
# Serveur avec token simple
mcp_client = MCPClientSSE(
sse_params=SSEServerParams(
url="https://mon-serveur.com/mcp",
headers={"Authorization": "Bearer mon_api_token_secret"},
timeout=60,
)
)
Côté serveur, validez le token dans un middleware :
from fastapi import FastAPI, Request, HTTPException
api = FastAPI()
@api.middleware("http")
async def verify_token(request: Request, call_next):
if request.url.path.startswith("/mcp"):
token = request.headers.get("Authorization", "")
if token != "Bearer mon_api_token_secret":
raise HTTPException(status_code=401, detail="Non autorisé")
return await call_next(request)
OAuth dans Le Chat de Mistral
Dans Le Chat, les connecteurs featured (GitHub, Gmail, Linear) gèrent OAuth automatiquement. Pour vos connecteurs custom, trois options sont proposées à la configuration :
- No authentication — URL publique
- Token — Vous collez un token manuellement
- OAuth 2.0 — Flux de consentement complet
Le token est stocké par utilisateur, pas par organisation. Si vous êtes dans une équipe Mistral, chaque membre a ses propres credentials.
Points clés à retenir
- OAuth 2.0 permet un consentement explicite avec des droits limités (scopes)
- Le flux : autorisation → code → tokens (access + refresh)
- Appliquez le principe du moindre privilège sur les scopes
- Les tokens doivent être stockés de manière sécurisée et renouvelés automatiquement
- Pour les cas simples, un token statique dans les headers HTTP suffit
- Dans Le Chat, les tokens sont stockés par utilisateur