HTTP MCP server: \ in --header Authorization not consistently resolved across tool calls in the same session

Status Open
Reported on v2.1.222
Maintainer reply None cached
Activity 0 comments · opened Sep 3, 2026

Bug : en-tête Authorization d'un serveur MCP HTTP (${VAR}) non réinjecté de façon fiable entre appels d'outils

Résumé

Un serveur MCP HTTP ajouté en scope local avec un en-tête Authorization: Bearer ${VAR}
(syntaxe d'expansion de variable d'environnement) ne voit son en-tête correctement résolu et
envoyé que sur le premier appel d'outil de la session. Les appels suivants dans la même
session échouent côté serveur avec une authentification refusée (401), alors que :

  • la variable d'environnement est présente, stable et inchangée pendant toute la session ;
  • le jeton qu'elle contient est valide, non expiré, non révoqué (vérifié côté serveur) ;
  • un appel HTTP direct (hors Claude Code) utilisant exactement le même en-tête réussit à

chaque tentative, y compris juste après un échec via l'outil MCP.

Le défaut est donc isolé au client MCP de Claude Code : soit l'expansion ${VAR} n'est
effectuée qu'une fois (par ex. à l'établissement de la connexion) puis n'est pas rejouée sur
les appels suivants, soit l'en-tête résolu n'est pas systématiquement réattaché à chaque
requête sortante du transport HTTP.

Environnement

  • Claude Code CLI : 2.1.222 (claude --version)
  • Composant client MCP tel que rapporté par le serveur dans ses propres logs :

claude-code 2.1.258 (chaîne clientInfo reçue lors du handshake initialize) — la
divergence de numéro avec claude --version n'a pas été creusée, elle vient peut-être d'un
composant versionné séparément.

  • OS : Windows 11 Pro (10.0.26200), shell PowerShell / Git Bash.
  • Serveur MCP : transport http, ajouté ainsi :

``bash
claude mcp add --transport http shiftcrm_recette https://crm-recette.shiftc.fr/mcp \
--header 'Authorization: Bearer ${SHIFTC_RECETTE_MCP_TOKEN}' \
--scope local
``

Config résultante (extrait de ~/.claude.json, projet courant) :

``json
"shiftcrm_recette": {
"url": "https://crm-recette.shiftc.fr/mcp",
"headers": { "Authorization": "Bearer ${SHIFTC_RECETTE_MCP_TOKEN}" }
}
``

  • SHIFTC_RECETTE_MCP_TOKEN : variable d'environnement utilisateur Windows (posée via

[Environment]::SetEnvironmentVariable(name, value, "User")), confirmée présente dans le
processus Claude Code après un redémarrage complet du client (claude mcp list ne remonte
plus d'avertissement « Missing environment variables » une fois la session relancée).

Reproduction

  1. Redémarrer entièrement Claude Code (fermeture complète, pas juste une nouvelle

conversation) après avoir posé/modifié la variable d'environnement portant le jeton.

  1. Premier appel d'un outil du serveur MCP concerné (ici cra_get_month, qui déclenche côté

serveur un GET /api/cra/months/2026-09) → succès, réponse 200, ~250-300 ms côté
serveur (chemin métier complet exécuté, confirmé dans les logs applicatifs).

  1. Tout appel suivant du même outil, ou d'un autre outil du même serveur, dans la même

session (aucun redémarrage entre-temps, variable d'environnement inchangée) → échec,
401 renvoyé par l'API, en 7-45 ms côté serveur (temps compatible avec un rejet précoce à
l'authentification, avant toute logique métier — voir « Isolation » ci-dessous).

  1. Répété sur plusieurs cycles complets de redémarrage : le schéma se reproduit à chaque fois

— un seul appel réussi juste après le redémarrage, puis échec systématique des suivants.

Isolation de la cause

Trois vérifications indépendantes écartent une cause côté jeton ou côté serveur ShiftCRM :

  1. Le jeton est valide. Lecture directe en base (assistant_accesses) : expires_at_utc

à echéance normale (~90 jours), revoked_at_utc NULL au moment des échecs.

  1. L'appel direct est fiable à 100 %. Plusieurs appels curl consécutifs, construits avec

le même en-tête Authorization: Bearer $SHIFTC_RECETTE_MCP_TOKEN (résolu par le shell, pas
par Claude Code), contre les mêmes routes (GET /api/cra/months/2026-09,
PUT /api/cra/months/2026-09/entries/bulk) → 200 à chaque tentative, y compris
immédiatement après un 401 obtenu via l'outil MCP avec, en théorie, le même jeton.

  1. Le relais serveur ne fait aucune mise en cache. Le code source du conteneur mcp

(CallerIdentityHandler.cs) lit l'en-tête Authorization de la requête HTTP entrante
à chaque requête via HttpContext.Request.Headers.Authorization et le retransmet tel
quel vers l'API — aucune résolution de variable d'environnement ni aucun état conservé
entre deux appels côté serveur. Le serveur ShiftCRM se contente de refuser un en-tête reçu
invalide ou absent ; il n'invente rien.

Ces trois points convergent vers une seule explication plausible : le client Claude Code
n'envoie pas systématiquement, à chaque appel d'outil MCP de cette session, un en-tête
Authorization correctement résolu depuis ${SHIFTC_RECETTE_MCP_TOKEN} — que ce soit parce
que l'expansion n'a lieu qu'une fois (à la connexion) et n'est pas rejouée, ou parce que la
requête sortante réutilise une configuration d'en-têtes capturée trop tôt sur une connexion
persistante.

Impact

Ce comportement touche directement l'usage documenté et recommandé (y compris par
Anthropic) de ${VAR} dans les en-têtes d'un serveur MCP HTTP pour éviter d'écrire un secret
en clair dans un fichier de configuration. Avec ce bug, n'importe quel serveur MCP HTTP
protégé par un jeton Bearer via variable d'environnement devient utilisable de façon
non fiable au-delà du tout premier appel d'une session — sans message d'erreur explicite
côté client (juste le rejet renvoyé par le serveur distant), ce qui rend le diagnostic difficile
pour qui n'a pas accès aux logs du serveur en question.

Contournement utilisé

En l'absence de correctif, l'appel HTTP a été reproduit manuellement (même route, même
schéma de payload que l'outil MCP concerné, même jeton, avec une clé d'idempotence) plutôt
que de continuer à dépendre de l'outil MCP dans cette session.

View original on GitHub ↗