8 min de lecture

Pourquoi les échecs de démarrage des serveurs MCP se ressemblent-ils ?

Les échecs de démarrage d'un serveur MCP deviennent diagnostiquables lorsque vous séparez les preuves du lancement, du handshake, de la découverte des outils et des actions externes.

Pourquoi les échecs de démarrage des serveurs MCP se ressemblent-ils ?

Un client MCP peut signaler qu'un serveur « n'a pas démarré » alors que le système d'exploitation a lancé le processus, que celui-ci a lu des données et que le serveur a déjà contacté un service extérieur à la machine. Ce message n'est pas un diagnostic. C'est une catégorie dans laquelle se retrouvent les échecs de lancement, de protocole, de découverte et parfois d'exécution des outils.

Considérez le démarrage comme une suite de frontières produisant des preuves. Si vous ne pouvez pas dire quelle frontière le serveur a franchie, vous ne pouvez pas indiquer à un opérateur si une nouvelle tentative est sans danger, si un identifiant a pu être utilisé ou si le client n'a simplement pas réussi à afficher un serveur sain. La solution n'est pas d'augmenter le délai d'attente. Il faut séparer les états et rendre chacun observable.

Un seul statut rouge masque quatre échecs différents

Un opérateur a besoin de quatre réponses, dans l'ordre : le client a-t-il lancé la commande configurée, les deux côtés ont-ils terminé l'initialisation MCP, le client a-t-il reçu une liste d'outils utilisable et du code a-t-il atteint un canal externe ? Chaque réponse prouve quelque chose de différent.

Un processus peut échouer avant même d'exister au sens habituel. L'exécutable peut manquer, le répertoire de travail peut ne pas exister, un exécuteur de paquets peut échouer avant d'appeler votre code ou le processus enfant peut se terminer immédiatement parce qu'une variable d'environnement obligatoire est absente. Appelez cela un échec de lancement. Il n'y a pas de session MCP et votre code applicatif n'a peut-être jamais été exécuté.

Un processus peut aussi exister tout en échouant lors de l'échange du protocole. Avec stdio, le processus enfant possède une entrée standard et une sortie standard reliées au client. Le serveur doit lire le JSON-RPC depuis stdin et écrire uniquement des messages JSON-RPC sur stdout. Il doit ensuite répondre à la requête initialize du client avec une version de protocole et des capacités compatibles. Le client envoie ensuite notifications/initialized. Si cette séquence ne se termine pas, il s'agit d'un échec du handshake.

Un handshake réussi ne prouve pas que le client a découvert les outils. Un serveur peut déclarer la prise en charge des outils, puis échouer lors de leur enregistrement, produire un schéma d'entrée invalide, renvoyer un résultat tools/list mal formé ou renvoyer une liste vide parce que sa propre configuration a désactivé tous les outils. Appelez cela un échec de découverte des outils. Le client et le serveur peuvent être suffisamment opérationnels pour échanger des messages, sans qu'aucun outil ne soit disponible pour l'agent.

Enfin, un serveur peut terminer la découverte et échouer uniquement lorsqu'un outil est exécuté. Il s'agit d'un échec d'exécution d'un outil. Il doit figurer dans un autre rapport d'incident. Si vous le mélangez au démarrage, quelqu'un finira par relancer un serveur qui a déjà envoyé une requête HTTP ou ouvert une connexion SSH.

La documentation de Model Context Protocol rend cette séparation visible, même si de nombreuses interfaces clientes ne la montrent pas. Ses recommandations de débogage distinguent les problèmes de processus et de configuration de la journalisation du protocole, et rappellent que les serveurs locaux stdio doivent garder stdout libre de tout journal ordinaire. Le cycle de vie de l'initialisation du protocole et la requête tools/list sont deux échanges distincts. Conservez cette distinction dans votre propre télémétrie au lieu d'accepter l'étiquette générique du client.

L'échec de lancement survient avant MCP

Un échec de lancement signifie que le client n'a pas obtenu de processus enfant utilisable avec un flux de protocole lisible. Cela ne signifie pas que la commande « avait l'air correcte » dans un terminal.

Les shells interactifs masquent beaucoup de choses. Votre shell dispose d'un PATH, d'un répertoire courant, de gestionnaires de versions du langage, d'identifiants et de fichiers de configuration que n'hériteront pas forcément une application de bureau ou un sous-processus d'agent. Un client peut se lancer avec / comme répertoire de travail sur macOS. Il peut utiliser un environnement restreint. Il peut transmettre la commande sous la forme d'un exécutable accompagné d'un tableau d'arguments plutôt que par l'intermédiaire d'un shell, auquel cas les alias et redirections du shell ne servent à rien.

Capturez l'enregistrement exact du lancement avant de tenter de raisonner sur MCP :

run_id=run_01JX...
phase=launch
command=/usr/local/bin/node
argv=["/Users/dev/work/acme-mcp/dist/index.js"]
cwd=/
pid=84217
started_at=2026-07-22T14:03:12.417Z

Capturez ensuite un événement terminal lorsque le processus se termine ou lorsque le délai du handshake expire :

run_id=run_01JX...
phase=launch
exit_code=1
signal=null
stderr=Error: ENOENT: no such file or directory, open './config.json'

Cet enregistrement tranche rapidement un débat fréquent. Le serveur n'avait pas « un problème MCP ». Il supposait qu'un chemin relatif serait résolu depuis le répertoire du projet, mais le client l'a lancé depuis /.

Utilisez des chemins absolus pour l'exécutable, le point d'entrée, les fichiers de configuration et tous les fichiers lus au démarrage. Le guide de débogage MCP signale explicitement les répertoires de travail indéfinis pour les serveurs lancés par un client et recommande les chemins absolus. Ce n'est pas une précaution de style. Elle supprime une source d'échecs qui n'apparaissent que lorsqu'une même configuration est installée sur une autre machine.

Ne déclarez pas le lancement réussi simplement parce que vous avez reçu un PID. Un PID indique que le noyau a créé un processus. Il ne dit rien sur le chargement du programme, l'intégrité du canal stdout ou le fait que le processus soit déjà devenu un zombie en attente d'être récupéré.

Une machine à états utile pour le lancement reste réduite :

not_requested
  -> spawn_requested
  -> spawned
  -> executable_ready
  -> handshake_pending

spawned signifie que le parent a reçu le PID de l'enfant. executable_ready signifie que l'enfant a écrit sur stderr un événement explicite de disponibilité, sans rapport avec le protocole, après avoir chargé sa configuration et installé son gestionnaire d'erreurs fatales. N'envoyez pas cet événement sur stdout. Pour un serveur stdio, stdout n'est pas un canal de journalisation placé par hasard à proximité. C'est le fil du protocole.

L'événement de disponibilité ne doit pas prétendre que le serveur est connecté à une API, une base de données ou un hôte distant. Il doit indiquer uniquement ce qu'il prouve : le processus a atteint la configuration de son transport MCP. Une ligne telle que ready=true devient trompeuse lorsque les équipes l'interprètent discrètement comme « les appels d'outils sont sans danger ». Nommez plutôt la phase.

Le handshake a une définition précise

Un échec du handshake MCP commence après l'existence d'un processus utilisable et se termine avant la fin du cycle de vie de l'initialisation. Ne l'appelez pas échec de connexion sans vérifier les messages.

Avec un transport stdio, les premiers octets de stdout sont importants. Une bannière de démarrage peut corrompre le flux avant même que votre serveur ne voie la requête. Il en va de même pour une dépendance qui affiche une notification de mise à jour, un console.log, un print Python, un formateur d'exception de framework ou un script wrapper qui écrit un statut sur stdout. Les recommandations officielles de compilation et de débogage MCP sont claires : pour les serveurs stdio, écrivez les journaux sur stderr, car stdout transporte les messages du protocole.

Voici la plus petite trace utile d'une initialisation saine :

{"direction":"in","id":1,"method":"initialize"}
{"direction":"out","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"acme","version":"1.4.0"}}}
{"direction":"in","method":"notifications/initialized"}

La version exacte du protocole dépend des versions prises en charge par le client et le serveur. Le point important est que le serveur a sélectionné une version acceptée par le client, renvoyé un résultat valide et reçu la notification de fin. Stockez un événement analysé pour chaque message, et non des charges utiles brutes susceptibles de contenir des identifiants.

Si votre trace commence plutôt ainsi, le diagnostic change :

stdout: Starting Acme MCP server
{"jsonrpc":"2.0","id":1,"method":"initialize",...}

Le serveur est peut-être parfaitement capable de répondre, mais l'analyseur JSON du client vient déjà de rencontrer une entrée invalide. Un délai d'attente après ce point ne signifie pas que le serveur était lent. Il indique que le transport a été corrompu.

Un autre échec courant semble plus sain :

phase=handshake
initialize_received=true
initialize_response_sent=false
fatal_error=Cannot read properties of undefined (reading 'tools')

L'enfant a été lancé. Il a reçu la requête. Il a échoué en préparant la réponse. Il s'agit d'un bug du serveur ou d'une hypothèse de configuration non gérée, pas d'une mauvaise configuration du client.

Rendez les frontières explicites dans les journaux :

phase=handshake event=initialize_received run_id=run_01JX request_id=1
phase=handshake event=initialize_responded run_id=run_01JX request_id=1 protocol_version=2025-06-18
phase=handshake event=initialized_received run_id=run_01JX

Si vous écrivez seulement connected=true, vous effacez l'information qui distingue une réponse envoyée d'un cycle d'initialisation terminé. Les clients peuvent fermer ou redémarrer après avoir reçu la réponse, mais avant d'envoyer la notification. Sur le plan opérationnel, cela diffère d'une erreur d'analyse lors de l'initialisation.

Donnez au handshake sa propre échéance. Démarrez-la lorsque le processus est lancé, ou lorsque le transport est prêt si vous pouvez observer ce moment. Arrêtez-la à l'arrivée de notifications/initialized. À son expiration, indiquez le dernier événement confirmé, par exemple spawned_no_initialize, initialize_received_no_response ou response_sent_no_initialized. Ces noms indiquent à l'opérateur quelle partie examiner en premier.

La découverte des outils échoue après que le serveur est déjà joignable

Un échec de découverte signifie que le client et le serveur peuvent parler MCP, mais que le client n'a pas reçu de réponse exploitable à tools/list. Il est souvent signalé comme un échec de démarrage parce que de nombreux clients découvrent les outils immédiatement après l'initialisation.

Ne supposez pas qu'un panneau d'outils vide prouve que le résultat de tools/list était vide. Certains clients masquent les outils après un échec de validation du schéma. D'autres mettent en cache les résultats de découverte. Certains demandent les outils à la demande, uniquement lorsque l'agent commence une tâche. D'autres se connectent à un serveur MCP pour les ressources ou les invites et ne demandent jamais d'outils. Vos preuves doivent contenir la requête et la réponse.

Une trace de découverte saine ressemble à ceci :

{"direction":"in","id":2,"method":"tools/list"}
{"direction":"out","id":2,"result":{"tools":[{"name":"issue_lookup","description":"Fetch one issue by identifier","inputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}}]}}

Un enregistrement de découverte doit indiquer le nombre d'outils et une empreinte des schémas normalisés. Cette empreinte permet de vérifier que deux exécutions ont annoncé la même interface, sans conserver des descriptions ou une configuration sensibles. Elle révèle aussi les changements accidentels, par exemple lorsqu'un outil existe toujours mais que ses paramètres obligatoires ont disparu.

Ne construisez pas les définitions d'outils en contactant un service externe pendant tools/list. Cette conception transforme la découverte en effet de bord, fait d'un rafraîchissement du client une exécution et crée la pire question possible lors d'un incident : « Le simple fait de lister les outils a-t-il modifié quelque chose ? » L'enregistrement des outils doit être local et déterministe autant que possible.

Un serveur peut avoir besoin d'une configuration pour décider s'il annonce un outil. Lisez cette configuration au démarrage et enregistrez le résultat, mais ne faites pas attendre la découverte d'un outil un renouvellement de jeton ou une vérification SSH. Si un outil exige un identifiant, vérifiez la présence de sa référence locale sans l'utiliser. Différez l'action distante réelle jusqu'à l'appel de l'outil.

Cette distinction compte pour le contrôle des agents. Si un agent atteint Sallyport avec sp mcp, un enregistrement de découverte MCP réussi montre uniquement que le relais a exposé des opérations appelables, et non que Sallyport a effectué une action HTTP ou SSH.

Il existe une raison légitime de renvoyer une liste vide : aucun outil n'est activé pour la configuration actuelle du serveur. Indiquez-le dans une réponse structurée ou dans un journal visible par le client. Ne provoquez pas une erreur lors de l'enregistrement en laissant le client deviner si l'ensemble d'outils est volontairement vide.

phase=discovery event=tools_list_responded run_id=run_01JX tool_count=0 reason=no_enabled_tools

Cet enregistrement donne à l'opérateur un problème de configuration à corriger. Une erreur générique de démarrage ne lui donne qu'une habitude à répéter.

La joignabilité externe nécessite sa propre preuve

Contrôler les autorisations avant les appels externes
Un coffre verrouillé refuse chaque action. Une session MCP joignable n'est donc jamais confondue avec une autorisation d'agir.

La question « le processus a-t-il atteint un canal externe ? » ne peut pas être résolue par un PID, un handshake réussi ou une liste d'outils remplie. Il faut un événement à la frontière où votre code tente l'action externe.

Définissez précisément le canal externe. Dans ce contexte, il comprend une requête HTTP sortante, l'appel d'un utilitaire SSH, une connexion à une base de données extérieure au processus local, le renouvellement d'un identifiant cloud, la publication dans une file de messages ou tout appel susceptible de produire un effet ou de révéler des informations hors de la session MCP. La lecture d'un fichier de configuration local ne compte pas. Le chargement d'un identifiant depuis un coffre local protégé ne compte pas non plus à lui seul. Son envoi dans une requête, oui.

Enregistrez la tentative avant le début de l'appel, puis son résultat. Utilisez un identifiant d'action opaque qui peut être relié à l'identifiant de requête MCP et à celui de l'exécution du serveur.

run_id=run_01JX phase=execution event=external_attempt action_id=act_8Qf tool=issue_lookup channel=https host=api.example.test
run_id=run_01JX phase=execution event=external_result action_id=act_8Qf status=200 duration_ms=184

Ne placez pas dans ces enregistrements les en-têtes d'autorisation, les jetons bearer, les URL signées, les arguments de commande contenant des secrets ni les corps de réponse complets. Un journal d'incident qui divulgue l'identifiant qu'il devait aider à examiner aggrave l'incident.

L'emplacement de external_attempt n'est pas théorique. Placez-le trop tôt et vous affirmerez qu'un appel externe a eu lieu alors que le code n'a fait que préparer un objet de requête. Placez-le trop tard et un délai d'attente ou un plantage du processus peut laisser un trou alors que des octets ont déjà quitté la machine. Émettez-le immédiatement avant l'appel de bibliothèque qui peut déclencher une activité réseau ou SSH. Si la bibliothèque fournit un point d'observation plus bas niveau pour la connexion ou la requête, enregistrez un second événement uniquement si vous pouvez le faire sans brouiller le sens de « tentative ».

Un exemple d'échec montre pourquoi c'est important. Un opérateur ajoute un serveur MCP qui lit un jeton de suivi des problèmes lors de l'initialisation du module et appelle un point de terminaison « qui suis-je » pour le valider. Le processus enfant démarre, écrit une ligne de débogage sur stdout et corrompt le premier message MCP. Le client affiche « le serveur n'a pas démarré ». L'équipe redémarre le client deux fois.

Sans enregistrements par phase, elle conclut qu'aucune requête n'a quitté la machine puisque le serveur n'est jamais apparu dans l'interface du client. Cette conclusion est fausse. L'appel d'initialisation du module a été exécuté avant l'envoi de initialize par le client et a contacté trois fois le suivi des problèmes. L'état de l'interface ne disait rien sur la joignabilité externe.

Déplacez la vérification d'identité dans un outil explicite en lecture seule, ou faites-en une partie de la première action qui a réellement besoin du service distant. Enregistrez-la alors comme une exécution d'outil. Le serveur peut se lancer, s'initialiser et lister ses outils sans toucher au réseau. L'opérateur peut désormais distinguer « le serveur est disponible » de « l'identifiant et le service distant ont fonctionné ». Ce sont deux faits distincts qui doivent le rester.

Un registre des phases transforme les incidents vagues en affirmations vérifiables

Créez une entrée de registre par exécution et ajoutez-y des événements de phase immuables. Il n'est pas nécessaire de construire un moteur de règles complexe. Il faut des noms stables, des horodatages et suffisamment de champs de corrélation pour reconstituer les faits.

Utilisez cette structure :

{
  "run_id": "run_01JX",
  "server_name": "acme",
  "pid": 84217,
  "phase": "discovery",
  "event": "tools_list_responded",
  "request_id": 2,
  "tool_count": 4,
  "at": "2026-07-22T14:03:13.083Z"
}

Le registre doit consigner des événements et non des conclusions collées dans une chaîne. phase=handshake et event=initialize_received peuvent être comptés, interrogés et testés. message="MCP seems stuck" ne le peut pas.

Gardez un modèle d'état volontairement simple :

  1. spawn_requested, spawned, executable_ready et exited appartiennent au lancement.
  2. initialize_received, initialize_responded et initialized_received appartiennent au handshake.
  3. tools_list_received et tools_list_responded appartiennent à la découverte.
  4. tool_call_received, external_attempt et external_result appartiennent à l'exécution.
  5. revoked, terminated et client_disconnected décrivent une interruption, pas un succès.

La distinction que les équipes confondent souvent est l'établissement de la session et l'autorisation d'agir. Un serveur peut établir une session MCP sans avoir la permission d'utiliser un identifiant ou de se connecter à un système distant. Si vous assimilez ces deux états, un événement d'approbation peut ressembler à un événement de connectivité et une action refusée à un échec de démarrage.

Conservez les événements d'autorisation à côté de l'appel qu'ils régissent. Par exemple, enregistrez authorization_requested et authorization_granted après tool_call_received, mais avant external_attempt. L'opérateur pourra alors affirmer, preuves à l'appui, que la requête de l'outil est arrivée, que l'utilisateur l'a refusée et qu'aucune tentative externe n'a eu lieu. C'est beaucoup plus précis que de dire que la requête « n'est pas allée au bout ».

Utilisez un identifiant d'exécution valable pour un seul processus enfant. Ne réutilisez pas le nom du serveur comme identifiant de corrélation. Un client peut lancer deux copies du même serveur, redémarrer l'une après un délai et conserver d'anciennes métadonnées d'outils. Des identifiants réutilisés transforment ces tentatives distinctes en une histoire inventée.

Hachez ou masquez les valeurs qui révèlent des données utilisateur. Vous avez généralement besoin du nom de l'outil, de l'hôte du point de terminaison, de la classe de statut, de la classe d'erreur et de la durée. Vous avez rarement besoin de la chaîne de requête, du corps de la requête ou de la réponse. Les opérateurs doivent prouver qu'une frontière a été franchie, pas rejouer les données d'un utilisateur depuis les journaux.

Testez les frontières sans faire confiance au client complet

Séparer le démarrage des actions
sp mcp achemine les actions HTTP et SSH de l'agent via Sallyport, au lieu de remettre les identifiants à l'agent.

Un client complet est utile pour les tests d'intégration, mais c'est un mauvais premier témoin. Son interface peut regrouper les erreurs, mettre en cache les capacités, redémarrer les processus enfants et appliquer son propre délai. Testez chaque frontière par un chemin plus étroit avant d'accuser le serveur ou le client.

Commencez par la commande exacte, l'environnement et le répertoire de travail utilisés par le client. Ne remplacez pas la commande configurée par npm run dev. Ne l'exécutez pas depuis le dossier du projet si le client la lance ailleurs. Redirigez stderr vers un fichier pour l'examiner, mais laissez stdout intact si un autre processus doit y parler MCP.

Pour un serveur stdio, utilisez MCP Inspector comme premier test du protocole. La documentation MCP recommande Inspector pour tester les serveurs sur différents transports, et le projet Inspector peut lancer directement une commande stdio. Il permet d'observer l'échange d'initialisation et d'appeler tools/list sans deviner ce qu'un client de bureau a fait du résultat.

Réduisez ensuite le test à trois vérifications :

1. Does the configured command remain alive long enough to receive initialize?
2. Does it return a valid initialize response and receive initialized?
3. Does tools/list return the expected tool names and schemas?

Ce n'est qu'après ces vérifications que vous devez appeler un outil qui atteint un système externe. Choisissez une action en lecture seule avec une cible sans danger. Vérifiez que le registre d'exécution contient un external_attempt et un résultat final. Si un appel peut modifier des données, testez-le dans un environnement jetable ou fournissez une opération dédiée en mode simulation qui ne contacte pas le point de terminaison de production.

Le dépôt officiel de MCP Inspector est utile ici parce qu'il rend visible la frontière du transport. Ce n'est pas un proxy d'interception du trafic réseau de votre serveur. Il agit comme un client MCP pour le serveur sélectionné et fournit une interface de navigateur pour le test. Cette différence compte lorsque vous recherchez une corruption du transport : Inspector peut reproduire le côté client du protocole, mais ne peut pas prouver ce qu'un autre client de production a écrit dans le canal.

Pour les transports HTTP, ajoutez des preuves HTTP sans les confondre avec l'état MCP. Enregistrez la méthode HTTP, le chemin du point de terminaison, le statut, l'identifiant de session lorsqu'il existe et indiquez si la réponse contenait du JSON ou commençait un flux d'événements. Une connexion TCP ou un HTTP 200 ne signifie pas automatiquement que l'initialisation MCP est terminée. Appliquez les mêmes enregistrements de cycle de vie une fois la requête HTTP arrivée sur votre serveur.

Conservez dans votre suite de tests un serveur fictif qui échoue volontairement à chaque frontière. Un scénario se termine avant de lire les données. Un autre écrit hello sur stdout avant de répondre. Un troisième répond à initialize, puis renvoie un schéma d'outil invalide. Un quatrième liste un outil dont le gestionnaire enregistre une tentative externe et renvoie une erreur contrôlée. Si votre intégration cliente transforme les quatre cas en la même alerte, corrigez l'intégration avant qu'un vrai serveur ne vous oblige à déboguer à l'aveugle.

Les délais et les nouvelles tentatives doivent appartenir à une phase

Garder les secrets HTTP hors des agents
Sallyport injecte les identifiants bearer, basic ou d'en-têtes personnalisés pour HTTP sans les exposer à l'agent.

Un seul délai de démarrage pousse vers une mauvaise correction. Il donne la même apparence à un téléchargement de paquet lent, une erreur d'analyse lors de l'initialisation, une exception de schéma et un blocage de l'API distante. Utilisez des échéances distinctes, car chacune relève d'un responsable différent.

Le lanceur est responsable de la période allant de spawn_requested à spawned. Le serveur et le transport sont responsables de la période allant du lancement à initialized_received. Le chemin d'enregistrement du serveur est responsable de la découverte. Le gestionnaire de l'outil et sa dépendance distante sont responsables de l'exécution. Nommez le délai d'après son responsable et émettez le dernier événement de phase confirmé.

error=handshake_timeout last_event=initialize_received run_id=run_01JX

Ce message est exploitable. Il demande au responsable du serveur d'examiner la construction de la réponse et stderr, pas l'API distante.

Une nouvelle tentative automatique n'est sûre que lorsque la phase échouée n'a aucun effet externe. Réessayer un lancement échoué parce que l'exécutable était temporairement indisponible peut être acceptable. Réessayer une requête de découverte l'est généralement si la découverte est locale et sans effet. Réessayer un appel d'outil après external_attempt est dangereux, sauf si l'opération distante dispose d'un mécanisme documenté d'idempotence et que vous lui associez une valeur d'idempotence.

Ne dissimulez pas une nouvelle tentative derrière un redémarrage du serveur. Si le code de démarrage renouvelle un jeton, crée un tunnel, envoie un événement de télémétrie ou valide une identité distante, le redémarrage constitue déjà une action externe. C'est une raison supplémentaire de garder le démarrage local et de déplacer le travail distant dans des outils explicites.

Lorsqu'un client arrête un enfant après une échéance, émettez si possible un événement d'interruption avant la terminaison. Le serveur peut ne pas avoir le temps de l'envoyer. Le parent doit enregistrer la demande d'arrêt comme son propre événement, avec le dernier événement enfant qu'il a observé. Vous conservez ainsi un relevé honnête : le processus était peut-être sur le point de répondre, mais vous ne prétendez pas qu'il l'a fait.

Rendez le démarrage ennuyeux avant de le rendre rapide

Un bon serveur MCP peut démarrer sans réseau, sans utiliser d'identifiant, sans effet de bord modifiable et sans ambiguïté sur son état de protocole. Il charge une configuration locale, installe son transport, répond à l'initialisation et annonce une interface déterministe. Ce comportement est plus facile à exploiter et plus sûr à réessayer.

La plus mauvaise recommandation répandue consiste à « tout vérifier au démarrage ». Elle semble responsable parce que les erreurs apparaissent tôt. En pratique, elle mélange la configuration locale, l'identité, la disponibilité distante et l'autorisation dans un rituel opaque. Elle incite aussi les clients à réessayer des actions externes sous une étiquette d'erreur de démarrage.

Validez localement ce qui peut l'être. Signalez la joignabilité distante au moyen d'un outil explicite ou de la première opération qui en a besoin. Gardez les noms des phases stables. Pour stdio, réservez stdout au protocole. Testez un serveur volontairement défectueux pour chaque frontière que vous prétendez observer.

Lorsque le prochain client dira qu'un serveur MCP n'a pas démarré, vous devriez pouvoir répondre à quatre questions à partir d'un seul enregistrement d'exécution : le processus a-t-il été lancé, l'initialisation est-elle terminée, les outils étaient-ils découvrables et quelque chose a-t-il atteint le monde extérieur ? Si vous ne pouvez pas répondre aux quatre, le statut reste ambigu.

FAQ

Pourquoi mon client MCP affiche-t-il une seule erreur pour plusieurs problèmes de démarrage différents ?

Traitez le lancement du processus, l'initialisation du protocole, la découverte des capacités et la première action externe comme des états distincts. Un client peut afficher une erreur générique alors qu'un seul de ces états a échoué. Vos journaux doivent indiquer quelle frontière a été franchie à chaque étape.

Un processus MCP en cours d'exécution signifie-t-il que le serveur s'est connecté correctement ?

Non. Un processus en cours d'exécution prouve seulement que le système d'exploitation l'a créé et qu'il n'est pas terminé. Il peut être bloqué pendant le chargement de sa configuration, attendre une dépendance, écrire des données parasites sur stdout ou ignorer la requête initialize du client.

Des journaux écrits sur stdout peuvent-ils casser un serveur MCP stdio ?

Pour un serveur stdio, stdout est le canal du protocole. Une bannière, une trace d'erreur, une notification du gestionnaire de paquets ou une instruction d'affichage ordinaire peut corrompre le flux JSON-RPC avant que le serveur ne réponde à initialize. Envoyez plutôt les informations de diagnostic vers stderr.

Qu'est-ce que le handshake d'initialisation MCP ?

Le client envoie une requête initialize, le serveur renvoie une version de protocole et des capacités compatibles, puis le client envoie la notification notifications/initialized. Un serveur qui démarre mais ne termine jamais cet échange a un échec de handshake, pas un problème lié aux outils.

Que signifie l'échec de la découverte des outils MCP ?

Cela signifie que le client a terminé l'initialisation, mais n'a pas obtenu de résultat exploitable de tools/list. La cause peut être un gestionnaire manquant, une exception lors de la construction des schémas, une déclaration de capacité non prise en charge ou un client qui ne demande aucun outil.

Un serveur MCP doit-il contacter une API externe pendant son démarrage ?

N'intégrez pas de requête réseau, de connexion SSH, de renouvellement de jeton ou de recherche de secret au démarrage du serveur, sauf si le serveur ne peut réellement pas fonctionner sans cela. Démarrez d'abord le point d'accès du protocole, puis effectuez l'appel externe dans le gestionnaire de l'outil et enregistrez-le comme une action distincte.

Comment reproduire un échec de démarrage MCP en dehors de mon client ?

Exécutez la même commande de lancement avec le même utilisateur, le même répertoire de travail et le même environnement que le client. Testez ensuite le serveur avec MCP Inspector ou avec un échange JSON-RPC contrôlé. Un test effectué dans un terminal avec votre shell interactif peut masquer le véritable problème.

Que dois-je journaliser pour diagnostiquer le démarrage d'un serveur MCP ?

Utilisez un identifiant d'exécution unique dès le lancement et associez-le aux événements stderr, aux événements du protocole, à la découverte des outils et à chaque requête sortante. Enregistrez l'identifiant du processus, le chemin de l'exécutable, le statut de sortie et un identifiant d'action externe. N'enregistrez pas les identifiants ni les corps de requête par défaut.

Quelle doit être la durée du délai d'attente au démarrage de MCP ?

Une limite de temps est utile, mais elle doit indiquer la phase qu'elle protège. Utilisez une échéance courte pour le lancement, une autre pour l'initialisation et une autre pour tools/list. Sinon, un message de délai unique efface le diagnostic dont vous avez besoin.

Est-il sûr de réessayer automatiquement le démarrage d'un serveur MCP ayant échoué ?

Non. Réessayer un lancement échoué peut répéter des effets de bord si le code de démarrage crée des fichiers, renouvelle des jetons ou contacte des services. Ne réessayez qu'une phase dont vous savez qu'elle n'a aucun effet externe, et attribuez un identifiant d'exécution différent à chaque tentative.

Sallyport

Sallyport exécute les appels d'API et les commandes SSH à la place de votre agent IA. Les clés restent dans un coffre-fort local sur votre Mac ; vous approuvez chaque exécution et chaque action est consignée dans un journal scellé.

© 2026 Sallyport · Open source sous Apache-2.0 · Oleg Sotnikov