# Des enregistrements de serveurs MCP en double s'exécutent-ils deux fois ?

Un enregistrement MCP en double est rarement un simple élément de configuration superflu. Lorsque deux entrées décrivent le même serveur sous des noms différents, un agent peut recevoir deux chemins vers la même capacité. Avec stdio, cela signifie souvent deux processus enfants. Avec un endpoint distant, cela peut vouloir dire deux connexions authentifiées, deux inventaires d'outils et deux endroits indépendants où l'agent risque de faire le mauvais appel.

Le plus gênant, c'est que la priorité des configurations ne résout pas ce type de problème. Elle décide seulement de ce qui se passe lorsque des entrées entrent en conflit par leur nom. Elle ne permet pas de savoir si `repo-api`, `internal-api` et `my-api` sont trois noms pour un même exécutable et un même compte. Considérez l'identité de l'enregistrement comme un sujet opérationnel, pas comme une question de nommage.

## Les enregistrements en double créent des chemins d'exécution distincts

Deux entrées de serveur MCP distinctes peuvent lancer deux fois la même chose, car le client considère les enregistrements comme des définitions de connexion, et non comme des alias à dédupliquer. La spécification du transport MCP indique qu'un client lance un serveur stdio comme sous-processus et échange du JSON-RPC par l'entrée et la sortie standard de ce processus. Si deux entrées configurées invoquent la même commande, le résultat habituel est la création de deux sous-processus, chacun avec son initialisation et sa durée de vie propres.

Cela ne signifie pas qu'un agent appellera mécaniquement chaque outil deux fois. Les modèles choisissent l'outil à appeler. En pratique, le risque est plus subtil : l'agent peut voir des outils aux descriptions similaires dans les deux enregistrements, appeler l'un au premier tour puis l'autre lors d'une nouvelle tentative, ou utiliser les deux parce que leurs noms suggèrent des responsabilités différentes. De plus, le démarrage du serveur peut déjà avoir des effets avant le premier appel d'outil.

J'ai vu des serveurs qui semblaient passifs jusqu'à l'examen de leur phase d'initialisation. Ils renouvellent un jeton d'accès, créent un répertoire de cache, ouvrent une base SQLite locale, lancent une boucle d'interrogation pour maintenir un index à jour ou enregistrent un consommateur de webhook. Aucun de ces choix ne viole MCP. Ils deviennent problématiques lorsqu'une équipe suppose que « serveur MCP » signifie un objet unique et inerte.

Un serveur distant change la forme du problème, mais pas la nécessité de le prévenir. Streamable HTTP est conçu pour un processus serveur indépendant capable de gérer plusieurs connexions clientes. C'est utile lorsque plusieurs clients sont prévus. Cela signifie aussi que le service peut voir deux connexions qui prétendent toutes deux provenir de l'agent du même développeur, sauf si le service sait clairement les distinguer et les limiter.

La première question de diagnostic est donc simple : ces deux entrées créent-elles deux chemins d'exécution vers la même autorité externe ? Si oui, ce sont des doublons, même si leur JSON diffère et que leurs noms semblent cohérents.

## Le nom d'un serveur n'est pas son identité

Un enregistrement MCP possède au moins deux identités, que les équipes confondent souvent.

L'**identité d'affichage** est le nom configuré, par exemple `repo-api` ou `staging-db`. Elle compte parce que le client l'utilise pour présenter les outils et résoudre les conflits de configuration. Elle sert aux personnes et à la gestion interne du client.

L'**identité d'exécution** correspond à ce que l'enregistrement atteint réellement : un exécutable avec ses arguments et son environnement pertinent, ou une URL distante avec son contexte d'authentification. Elle concerne les processus et les systèmes externes.

Dans une configuration bien organisée, ces identités correspondent. Ce n'est pas obligatoire. Supposer le contraire permet aux doublons de passer les revues.

Prenons ces entrées :

```json
{
  "mcpServers": {
    "billing": {
      "command": "python3",
      "args": ["tools/billing_mcp.py", "--account", "prod"]
    },
    "finance-tools": {
      "command": "python3",
      "args": ["tools/billing_mcp.py", "--account", "prod"]
    }
  }
}
```

Les noms diffèrent, mais il s'agit d'un même programme avec les mêmes arguments. À moins que le programme n'impose lui-même une instance unique, ces entrées lancent deux processus.

Voici une variante moins évidente :

```json
{
  "mcpServers": {
    "deploy": {
      "command": "./bin/deploy-mcp",
      "args": ["--workspace", "/Users/dev/work/acme"]
    },
    "release-helper": {
      "command": "node",
      "args": ["scripts/mcp-launch.js", "deploy", "--workspace", "/Users/dev/work/acme"]
    }
  }
}
```

Une comparaison textuelle indique que ces entrées sont différentes. Une comparaison au niveau des processus peut montrer que le wrapper lance le même exécutable `deploy-mcp` avec le même espace de travail. Voilà pourquoi la revue de configuration a besoin d'une règle d'identité, et pas d'une simple recherche de lignes dupliquées.

Comparez les entrées dans cet ordre :

1. Comparez l'endpoint distant normalisé ou l'exécutable final lancé par la commande.
2. Comparez les arguments qui déterminent le compte, le locataire, le dépôt, l'espace de travail ou la cible d'écriture.
3. Comparez le répertoire de travail et les noms des variables d'environnement non secrètes qui modifient le comportement.
4. Comparez séparément les détenteurs des identifiants. Deux entrées qui atteignent le même endpoint avec des autorités différentes ne sont pas des doublons inoffensifs. C'est un choix de permissions qui doit être justifié.

Ne comparez pas les valeurs secrètes pour effectuer cet audit. Elles ne sont pas nécessaires, et les copier dans la sortie d'audit créerait un second problème de sécurité. Notez qu'une entrée utilise `BILLING_TOKEN` et qu'une autre utilise `PERSONAL_BILLING_TOKEN`, puis déterminez si ces variables autorisent le même compte.

## La priorité des portées ne nettoie pas les noms distincts

Claude Code documente trois portées MCP : locale, projet et utilisateur. Les entrées de portée projet se trouvent dans un fichier `.mcp.json` du dépôt, tandis que les entrées de portée utilisateur sont disponibles dans tous les projets. Sa documentation indique également qu'un nom de serveur identique est résolu d'abord dans la portée locale, puis dans la portée projet et enfin dans la portée utilisateur. Une documentation antérieure appelait la portée utilisateur « globale ».

Ce comportement vous protège dans un seul cas : le même nom apparaît dans plusieurs portées. Il ne vous protège pas contre l'enregistrement en double le plus courant :

```text
Portée utilisateur : personnel-git    -> /Users/dev/bin/git-mcp
Portée projet :     depot-git         -> /Users/dev/bin/git-mcp
```

Les deux noms peuvent rester visibles, car aucun conflit de nom ne se produit. Les deux peuvent être lancés et exposer des outils presque identiques.

Un autre piège existe. Un développeur voit que le fichier du projet contient `depot-git`, ajoute `personnel-git` dans la portée utilisateur pour utiliser l'outil en dehors de ce dépôt, puis oublie que cette entrée utilisateur se charge aussi dans le dépôt. L'expérience immédiate est agréable. Le nettoyage attend jusqu'à ce qu'un appel d'outil écrive deux enregistrements d'audit ou qu'un processus d'arrière-plan verrouille le même répertoire d'état.

Utilisez les portées pour définir la propriété, pas par commodité :

- Placez un enregistrement dans la portée projet lorsque le dépôt en a besoin et que la configuration peut être partagée sans danger.
- Placez-le dans la portée utilisateur lorsqu'il s'agit d'un utilitaire personnel destiné à fonctionner dans plusieurs dépôts.
- Utilisez la portée locale pour une expérimentation privée et propre au dépôt, qui ne doit pas être validée.
- Ne dupliquez pas une entrée du projet dans la portée utilisateur. Si vous en avez besoin ailleurs, utilisez-la seulement là où la configuration du projet s'applique, ou définissez une entrée volontairement distincte avec une autre cible et un objectif documenté.

Un remplacement portant le même nom mérite aussi de l'attention. Il ne crée pas deux entrées actives comme peuvent le faire deux noms différents, mais il peut masquer une configuration d'équipe derrière une configuration personnelle. L'agent effectue alors des actions avec un exécutable privé ou un endpoint personnel, tandis que les réviseurs pensent que la définition du dépôt est utilisée. C'est un problème de provenance, pas de nombre de processus, mais il doit tout de même être corrigé.

## Prouver le doublon, de la configuration à l'appel

Ne supprimez pas la première entrée qui semble superflue. Établissez la chaîne entre la configuration, le processus et l'action externe. Vous éviterez ainsi une correction qui paraît propre, mais supprime discrètement la seule entrée utilisant le bon compte ou le bon espace de travail.

Commencez dans le dépôt concerné :

```sh
claude mcp list
claude mcp get depot-git
claude mcp get personnel-git
```

Anthropic documente `claude mcp list`, `claude mcp get` et `claude mcp remove` comme commandes normales de gestion. Servez-vous de leur sortie pour identifier chaque nom visible, puis examinez les entrées suspectes une par une.

Notez cinq faits pour chaque entrée dans un fichier temporaire : nom configuré, portée, commande ou URL, arguments et compte ou espace de travail externe atteint. Évitez de copier les valeurs d'environnement. Pour un serveur distant, notez l'hôte et le chemin, pas un en-tête d'autorisation.

Lancez ensuite une courte session d'agent et examinez les processus pendant sa connexion. Sur macOS ou Linux, remplacez `billing_mcp.py` par une partie unique de la commande attendue :

```sh
ps -ax -o pid,ppid,lstart,command | grep '[b]illing_mcp.py'
```

Un lancement stdio en double peut produire ceci :

```text
91204 91188 Tue Jul 21 10:14:07 2026 python3 tools/billing_mcp.py --account prod
91219 91188 Tue Jul 21 10:14:09 2026 python3 tools/billing_mcp.py --account prod
```

Les identifiants de processus diffèrent. Le parent peut être le même processus d'agent ou deux processus associés. La preuve importante est que les deux commandes ont la même identité d'exécution et des durées de vie qui se chevauchent.

Effectuez ensuite un appel d'outil volontairement sûr et en lecture seule. Choisissez un appel dont le résultat attendu est limité, par exemple la récupération de l'identifiant du compte actuel ou la liste d'un objet connu. Vérifiez les journaux du système cible, ceux du serveur ou votre journal d'actions. Si vous voyez deux connexions indépendantes pour un seul appel, vous avez trouvé un démarrage de serveur en double. Si vous voyez deux appels, déterminez si l'agent a sélectionné deux outils, réessayé après une erreur ou si le serveur a répété le travail. Ces causes sont différentes et nécessitent des corrections distinctes.

La spécification du cycle de vie MCP exige une initialisation avant le fonctionnement normal. Voir deux événements d'initialisation suffit à prouver l'existence de deux connexions. Cela ne prouve pas qu'une action métier a eu lieu. Ne dites donc pas aux personnes chargées de l'incident que « le déploiement a été exécuté deux fois » simplement parce que vous avez vu deux poignées de main.

## Empreinter les configurations sans lire les identifiants

Un contrôle utile des doublons produit une empreinte stable pour chaque entrée configurée et exclut les valeurs secrètes. Le script suivant lit un ou plusieurs fichiers JSON, extrait `mcpServers` et compare le transport, la commande, les arguments, l'URL, le répertoire de travail et les noms des variables d'environnement. Ne lui donnez que des fichiers que vous êtes autorisé à consulter.

```python
#!/usr/bin/env python3
# save as mcp_duplicates.py
import hashlib
import json
import pathlib
import sys
from collections import defaultdict

if len(sys.argv) < 2:
    raise SystemExit("usage: mcp_duplicates.py CONFIG [CONFIG ...]")

entries = defaultdict(list)

for raw_path in sys.argv[1:]:
    path = pathlib.Path(raw_path).expanduser()
    with path.open() as handle:
        document = json.load(handle)

    for name, server in document.get("mcpServers", {}).items():
        identity = {
            "type": server.get("type", "stdio"),
            "command": server.get("command"),
            "args": server.get("args", []),
            "url": server.get("url"),
            "cwd": server.get("cwd"),
            "env_names": sorted(server.get("env", {}).keys()),
            "header_names": sorted(server.get("headers", {}).keys()),
        }
        encoded = json.dumps(identity, sort_keys=True, separators=(",", ":"))
        fingerprint = hashlib.sha256(encoded.encode()).hexdigest()[:12]
        entries[fingerprint].append((str(path), name, identity))

for fingerprint, matches in sorted(entries.items()):
    if len(matches) < 2:
        continue
    print(f"DUPLICATE EXECUTION IDENTITY {fingerprint}")
    for path, name, identity in matches:
        print(f"  {path}: {name}")
        print(f"    {json.dumps(identity, sort_keys=True)}")
```

Exécutez-le sur le `.mcp.json` d'un projet et sur un export ou une copie nettoyée de la configuration utilisateur utilisée par votre client :

```sh
python3 mcp_duplicates.py .mcp.json ~/tmp/user-mcp.json
```

La sortie devrait ressembler à ceci :

```text
DUPLICATE EXECUTION IDENTITY 64e0e2509d8a
  .mcp.json: depot-git
    {"args":["tools/git_mcp.py"],"command":"python3","cwd":null,"env_names":["GIT_ACCOUNT"],"header_names":[],"type":"stdio","url":null}
  /Users/dev/tmp/user-mcp.json: personnel-git
    {"args":["tools/git_mcp.py"],"command":"python3","cwd":null,"env_names":["GIT_ACCOUNT"],"header_names":[],"type":"stdio","url":null}
```

Ce contrôle est volontairement prudent. Il signale les entrées qui ont la même forme d'exécution déclarée. Il ne peut pas prouver que deux wrappers différents ne convergent pas vers un seul processus, ni que deux URL différentes ne redirigent pas vers le même service. Considérez sa sortie comme une liste de vérifications, pas comme une liste de suppressions automatiques.

Attendez-vous aussi à des faux négatifs lorsqu'une configuration utilise un chemin relatif et l'autre un chemin absolu. Normalisez les chemins avant la comparaison si votre équipe utilise les deux formes. Faites-le dans un script contrôlé qui connaît la racine du dépôt. N'effectuez pas de recherche-remplacement général dans les fichiers de configuration.

## Deux instances indépendantes peuvent diverger sur l'état

Les problèmes les plus coûteux ne sont pas toujours les appels API en double. Deux instances peuvent diverger sur l'état local alors que chacune se comporte exactement comme prévu par son auteur.

Prenons un serveur qui conserve un cache local des métadonnées d'un dépôt. L'instance A démarre avec une copie plus ancienne et écrit des enregistrements de cache dans un répertoire partagé par défaut. L'instance B démarre après un changement de branche, lit le même répertoire et considère le cache comme valide parce que le fichier existe. Un appel d'outil renvoie alors des données qui ne correspondent à la vue actuelle d'aucun des deux processus. L'agent peut ensuite effectuer un appel parfaitement valide sur un objet obsolète.

Un autre problème fréquent concerne un consommateur de file d'attente. Les deux instances s'authentifient comme le même principal et interrogent le même flux de tâches. Si la file garantit une livraison au moins une fois, la gestion des doublons est peut-être déjà prévue. Si l'auteur de l'outil ajoute une carte locale de déduplication, chaque processus possède sa propre carte. Elle empêche les doublons dans un processus et ne fait rien entre les deux.

La mauvaise recommandation serait de « rendre le serveur sans état ». Elle est populaire parce qu'elle paraît sûre et que les services HTTP sans état gèrent bien de nombreuses connexions. Elle est incorrecte pour les outils locaux qui conservent volontairement des caches, l'état de renouvellement OAuth, des observateurs de fichiers ou des handles d'opération. La bonne exigence est plus précise : documenter si les instances concurrentes sont prises en charge, quelles ressources elles partagent et ce qui se passe lorsque deux instances utilisent la même identité.

Demandez aux responsables du serveur de répondre à ces questions dans le README ou dans la sortie de démarrage :

- Le démarrage écrit-il des fichiers locaux, renouvelle-t-il des identifiants ou lance-t-il une tâche d'arrière-plan ?
- Deux processus peuvent-ils utiliser le même espace de travail, le même compte et le même répertoire de cache ?
- Chaque appel d'outil qui modifie un système externe contient-il une clé d'idempotence ?
- Un opérateur peut-il identifier le processus client ou la session à l'origine d'un enregistrement ?

Si la réponse à la deuxième question est non, rendez le conflit explicite. Utilisez un verrou du système d'exploitation, un répertoire d'exécution unique par processus ou un bail côté serveur. Ne comptez pas sur le souvenir des utilisateurs pour qu'ils ne configurent l'outil qu'une seule fois.

## La duplication des outils et celle des actions sont deux incidents différents

Un client peut exposer deux outils similaires sans exécuter l'un ou l'autre deux fois. Il peut aussi effectuer une action externe répétée avec un seul outil. Les investigations déraillent lorsque les deux situations sont appelées « MCP en double ».

**La duplication de l'exposition des outils** signifie que deux enregistrements annoncent des capacités qui se chevauchent. L'agent peut voir `billing_get_invoice` depuis deux serveurs. C'est un risque lié à la configuration et aux instructions. Corrigez les enregistrements et leurs descriptions.

**La duplication de l'exécution** signifie que deux processus locaux ou deux sessions distantes existent. C'est un risque lié à la connexion et au cycle de vie. Corrigez le chemin d'enregistrement, le comportement de concurrence du serveur ou les deux.

**La répétition d'une action externe** signifie que le système cible a reçu plusieurs requêtes significatives. Elle peut provenir d'une exposition en double, de la logique de nouvelle tentative, de délais d'attente, d'une intervention de l'utilisateur, du serveur ou d'un bug client. Prouvez-la avec un identifiant d'opération dans le système cible, pas en déduisant quoi que ce soit du nombre de processus MCP.

Conservez ces informations ensemble pendant un incident :

```text
ID de l'exécution de l'agent :  run-7f3a
Nom configuré :                 depot-git
ID du processus serveur :       91204
Début de la connexion MCP :     2026-07-21T10:14:07Z
ID de la requête d'outil :      58
ID de l'opération cible :       commit-3a8b
```

Les identifiants n'ont pas besoin de porter exactement ces noms. Ils doivent permettre de relier l'agent, le serveur et le service cible. Si une couche ne peut pas produire de valeur de corrélation, indiquez-le dans le rapport d'incident au lieu de combler le manque par des suppositions fondées sur les horaires.

La séparation de Sallyport entre un journal Sessions pour les exécutions d'agents et un journal Activity pour les appels individuels est utile ici, car elle conserve cette distinction. Une seconde exécution ou connexion ne prouve pas automatiquement une seconde action externe ; les enregistrements d'appels doivent encore l'établir.

## Supprimer un enregistrement sans créer d'angle mort

Après avoir identifié un vrai doublon, choisissez un enregistrement canonique avant d'en supprimer un. L'entrée canonique doit avoir un responsable clair, une portée prévisible, une commande ou un endpoint vérifié et une source d'identifiants explicitement indiquée. « Cela fonctionnait sur ma machine » n'est pas un critère de sélection.

Pour une intégration appartenant à l'équipe, l'entrée du projet est généralement préférable, car elle peut être revue avec le code. Gardez les identifiants hors de ce fichier partagé. Claude Code prend en charge l'expansion de variables d'environnement dans `.mcp.json`, y compris dans les commandes, les arguments, les champs d'environnement, les URL et les en-têtes. Les définitions partagées sont ainsi possibles sans valider de jeton.

Pour un utilitaire personnel utilisé dans plusieurs projets, la portée utilisateur peut être la bonne place. Ne supprimez alors l'entrée du projet que si le dépôt n'exige pas une définition commune pour les autres contributeurs. Ne transformez pas une dépendance de l'équipe en prérequis personnel non documenté.

Après la modification, utilisez cette séquence de validation discrète :

1. Conservez l'entrée supprimée en dehors de la configuration active pendant toute la durée du test.
2. Lancez un processus d'agent vierge. Les processus existants peuvent conserver d'anciennes connexions.
3. Exécutez `claude mcp list` et inspectez l'entrée restante avec `claude mcp get <name>`.
4. Effectuez un appel sûr en lecture seule et notez une connexion et une requête cible.
5. Redémarrez encore une fois et vérifiez que l'enregistrement supprimé ne réapparaît pas.

Si la suppression casse un flux de travail, restaurez uniquement la définition canonique et corrigez son chemin, sa variable d'environnement ou ses permissions manquants. Ne restaurez pas les deux entrées comme solution rapide. Vous recréeriez l'ambiguïté que vous venez de diagnostiquer.

## Intégrer la détection des doublons à la revue de configuration

Le meilleur contrôle est une petite règle de revue : chaque enregistrement MCP doit avoir un responsable, une portée et une identité d'exécution unique pour son objectif. Cela suffit à éviter la plupart des erreurs avant le lancement des agents.

Placez le contrôle d'empreinte dans un script du dépôt si l'équipe conserve `.mcp.json` sous contrôle de version. Exécutez-le lors des revues locales et dans l'intégration continue sur la configuration partagée. Il ne verra pas les entrées de portée utilisateur d'un développeur. Ajoutez donc aussi `claude mcp list` à la liste de configuration des contributeurs qui signalent un comportement étrange des outils.

Pour la configuration utilisateur, tenez un inventaire court en dehors de la configuration elle-même. Une ligne par serveur suffit :

```text
personnel-git | utilisateur | outils Git dans tous les dépôts | responsable : développeur
depot-git | projet | flux de publication du dépôt | responsable : équipe plateforme
```

Si les deux lignes pointent vers le même exécutable et le même compte, l'une doit disparaître ou leurs cibles doivent être rendues volontairement distinctes. N'acceptez pas deux noms simplement parce que l'un paraît plus sympathique dans une consigne.

La discipline reste simple : une route pour un objectif, un responsable clairement visible et la preuve qu'une action demandée a produit un seul enregistrement externe. Une fois ces éléments en place, un processus en double devient un défaut observable, et non un mystère découvert tard dans la nuit derrière deux noms d'outils presque identiques.
