Blog • MCP
Après la mise à jour vers Claude Code 2.1.305, un serveur stdio de managed-mcp.json qui n'utilise une variable que dans env se charge à nouveau. Comment écrire deniedMcpServers ?
La note de Claude Code 2.1.305 tient en une phrase, et après la mise à jour un serveur peut apparaître seul : allowedMcpServers ne filtre plus les serveurs stdio de managed-mcp.json qui n'utilisent ${VAR} que dans env. S'il manquait à la liste autorisée, il se charge. Ce qui le garde arrêté est deniedMcpServers, pas une copie de plus de la liste autorisée.
Qui porte les permissions MCP est dans l'article sur la sécurité MCP. Quelle étape du journal arrête des identifiants vers le mauvais serveur d'autorisation est dans l'article OAuth précédent. Celui-ci ne traite que le changement de 2.1.305 : la liste autorisée n'arrête plus un serveur stdio qui n'utilise une variable que dans env, et le JSON de refus doit correspondre exactement à la commande.
- Le cas est un serveur stdio de managed-mcp.json dont ${VAR} n'apparaît que dans env.
- Depuis 2.1.305, la liste autorisée ne le filtre plus. S'il en était absent, il se charge après la mise à jour.
- Pour le garder arrêté, serverCommand dans deniedMcpServers doit être la commande plus les arguments, en tableau, dans le même ordre.
- Une liste de refus vide a une forme valide et ne bloque rien. serverName n'est pas un contrôle de sécurité.
La décision : un serveur stdio qui n'utilise ${VAR} que dans env n'est plus arrêté par la liste autorisée après 2.1.305. Écrivez dans la liste de refus un serverCommand identique à command et args.
Quel cas de la liste autorisée 2.1.305 change-t-il ?
Le journal des changements dit : allowedMcpServers cesse de filtrer les serveurs stdio de managed-mcp.json qui n'utilisent ${VAR} que dans env. Celui que la liste autorisée laissait de côté se charge à la mise à jour. Pour le garder arrêté, utilisez deniedMcpServers. Le journal est le journal des changements de Claude Code.
Le guide MCP pour les organisations énonce encore la règle précédente : les serveurs de managed-mcp.json sautent la liste autorisée, sauf une définition qui utilise ${VAR}, qui est encore contrôlée. Avant 2.1.259, chaque serveur géré devait passer une liste autorisée dès qu'elle existait. 2.1.305 retire une tranche étroite : stdio, et la variable seulement dans env. Le journal ne dit pas qu'un ${VAR} dans la commande, les arguments, l'URL ou headers est aussi dispensé. Le guide est le guide MCP pour les organisations.
Comment voir que la variable n'est que dans env ?
L'exemple stdio du guide a type, command, args et env. Ne mettez pas un secret dans une valeur d'env. Les identifiants par utilisateur utilisent ${VAR} et se lisent dans l'environnement de cet utilisateur. Le fichier ci-dessous ne laisse la variable que dans env. La commande et les arguments sont des littéraux. C'est le cas que nomme 2.1.305. Le chemin de commande est un exemple, pas un paquet à installer.
{
"mcpServers": {
"notes": {
"type": "stdio",
"command": "/usr/local/bin/notes-server",
"args": ["--stdio"],
"env": {
"NOTES_TOKEN": "${NOTES_TOKEN}"
}
}
}
}
Si ${VAR} est dans command, args, l'URL ou headers, le journal ne les met pas dans cette exemption. Traitez ce serveur comme encore soumis au contrôle de liste autorisée du guide, jusqu'à ce que le journal le dise autrement. L'exemple de liste de refus ici ne couvre que le cas env.
À quelle commande deniedMcpServers doit-il correspondre ?
Dans le guide, serverCommand correspond à la commande et aux arguments qui lancent un serveur stdio, et la correspondance est exacte. Le command ci-dessus plus args est ce tableau. Retirer un argument, en ajouter un, ou changer l'ordre, et cela ne correspond pas. serverName n'est qu'une étiquette. Le guide dit qu'une entrée serverName dans l'une ou l'autre liste n'est pas un contrôle de sécurité, parce qu'un utilisateur peut donner le même nom à n'importe quel serveur.
{
"deniedMcpServers": [
{
"serverCommand": ["/usr/local/bin/notes-server", "--stdio"]
}
]
}
La liste de refus s'applique aussi aux serveurs gérés, et le deniedMcpServers de l'utilisateur s'y joint, donc un utilisateur peut bloquer un serveur géré pour lui-même. Quand la liste autorisée est un tableau vide, le guide dit qu'aucun serveur n'est autorisé hormis ceux qui sautent le contrôle. Après 2.1.305, ce serveur stdio avec une variable seulement dans env est de ceux qui sautent, donc une liste autorisée vide ne le garde pas dehors.
Quel schéma fixe la forme de cette liste de refus ?
Utilisez JSON Schema pour fixer les types. deniedMcpServers doit être un tableau. Chaque serverCommand doit être un tableau de chaînes. Un tableau vide est une forme valide. Il ne prouve pas que ce serveur s'arrête. Ce qui l'arrête est la règle d'application : le tableau doit être identique à command plus args.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["deniedMcpServers"],
"properties": {
"deniedMcpServers": {
"type": "array",
"items": {
"type": "object",
"required": ["serverCommand"],
"properties": {
"serverCommand": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}
}
| Ce que l'enregistrement montre | Schéma | Si ce serveur doit rester arrêté |
|---|---|---|
| serverCommand est une seule chaîne | Échoue. Ce champ exige un tableau de chaînes. | Séparez d'abord en commande et argument. |
| Un argument est un nombre | Échoue. Chaque élément du tableau est une chaîne. | Passez à la même chaîne que dans args. |
| deniedMcpServers est un tableau vide, la forme est bonne | Passe. Un tableau vide est un tableau. | Ajoutez le serverCommand qui correspond à command et args. Une liste vide ne bloque rien. |
Des trois contrôles avant le chargement, lequel l'arrête vraiment ?
Le guide écrit les contrôles avant le chargement en trois étapes, et les relance quand un utilisateur reconnecte un serveur ou réactive un serveur désactivé dans /mcp. Les serveurs en processus de type sdk sautent les trois. Après une correspondance de liste de refus, rien ne l'annule.
| Étape | Ce serveur seulement dans env |
|---|---|
| Fusionner les listes | Les entrées de liste autorisée et de refus de chaque portée de réglages se combinent. Quand allowManagedMcpServersOnly est true, seule la liste autorisée gérée reste. La liste de refus fusionne toujours depuis chaque portée. |
| D'abord la liste de refus | Une correspondance exacte de serverCommand l'arrête. Après cette étape, la liste autorisée ne peut pas le remettre. |
| Ensuite la liste autorisée | Après 2.1.305, ce serveur stdio avec une variable seulement dans env saute cette étape. L'omettre de la liste autorisée ne l'arrête plus. |
Comment vérifier cette liste de refus dans JSONNote ?
- 1Choisir la bonne opération
Ouvrez JSON Schema. Réglez Fonctionnement sur Vérifiez avec Schema. Collez la liste de refus dans le premier champ et le schéma ci-dessus dans le second. serverCommand doit être un tableau de chaînes.
- 2Opposer quatre documents invalides
Mettez serverCommand en une seule chaîne, supprimez deniedMcpServers, mettez un argument en nombre, ou faites de deniedMcpServers un objet. Les quatre échouent. La liste de refus ci-dessus passe. Passer ne veut dire que la forme est bonne.
- 3Comparer la liste vide et cette commande
Ouvrez JSON Comparer Diff. Collez la liste de refus ci-dessus dans le premier champ. Dans le second, changez deniedMcpServers en tableau vide. Les deux passent le schéma. Ce qui arrête ce serveur est ce serverCommand, pas le tableau vide.
Questions fréquentes
Ajouter la même commande à la liste autorisée remplace-t-il la liste de refus ?
Non. Après 2.1.305, ce serveur saute la liste autorisée. Que la commande y figure ne décide plus s'il se charge. Pour l'arrêter, une entrée de refus doit correspondre. Après cette correspondance, la liste autorisée ne le remet pas.
Un serverName notes suffit-il ?
Non. Le guide dit que serverName est l'étiquette qu'un utilisateur attribue, et les jokers ne sont pas développés. Un utilisateur peut donner le même nom à n'importe quel serveur. Pour arrêter ce serveur stdio, utilisez serverCommand, identique à command et args.
S'il y a aussi ${VAR} dans la commande, se charge-t-il encore ?
Le journal n'inclut pas ce cas dans l'exemption. Il ne nomme que stdio, et une variable seulement dans env. Quand ${VAR} est aussi dans la commande, les arguments, l'URL ou headers, continuez à le traiter comme le contrôle de liste autorisée du guide. Si ce serveur ne correspond pas à la liste, ne supposez pas que 2.1.305 le chargera.
Les réglages de l'utilisateur peuvent-ils bloquer celui que l'organisation a déployé ?
Oui. Le guide dit que deniedMcpServers s'applique aux serveurs de managed-mcp.json, et que la liste de refus de l'utilisateur s'y joint. Un utilisateur peut en bloquer un pour lui-même. La liste autorisée ne remplace pas cette étape.
Le bloc env doit-il garder la valeur du jeton ?
Non. Le guide dit que toute personne sur la machine peut lire managed-mcp.json, donc ne mettez ni clé API ni autre identifiant dans env. Les identifiants par utilisateur utilisent ${VAR} et se lisent dans l'environnement de cet utilisateur. La liste de refus ne contient pas de jeton non plus, seulement la commande et les arguments.
Conclusion
2.1.305 n'exempte qu'un cas : un serveur stdio de managed-mcp.json dont ${VAR} n'est que dans env. La liste autorisée ne le filtre plus. Le journal n'exempte pas avec lui une variable dans la commande, les arguments, l'URL ou headers.
La décision : pour le garder arrêté, écrivez dans deniedMcpServers un serverCommand identique à command et args ; une liste autorisée vide ne le garde pas dehors ; serverName ne suffit pas ; ne stockez pas la valeur du jeton dans env.
Ensuite : fixez le tableau de refus avec le schéma, puis utilisez Diff pour voir le tableau vide face à ce serverCommand.