Skip to content

McpHub : gestion du cycle de vie des serveurs MCP

源码版本v4.0.10

Responsabilités

McpHub est le hub central par lequel Cline se connecte aux serveurs MCP externes. Il lit tous les serveurs configurés dans cline_mcp_settings.json, établit pour chacun une connexion client + transport indépendante, puis cache en mémoire les outils (tools), ressources (resources) et prompts exposés, à destination de l'agent principal. Toute la chaîne MCP — changement de configuration, surveillance de fichier, connexion/reconnexion, découverte des capabilities, jusqu'à l'appel tools/call — converge dans cette unique classe, et le Task principal n'a pas besoin de toucher directement au SDK MCP.

Sa position se situe entre « la boucle principale de l'agent » et « le protocole MCP concret ». Quand le Task veut appeler un outil MCP, il sort par McpHub.callTool (callTool:1233) ; quand l'UI veut lister les serveurs actuels, elle appelle getServers() pour obtenir un snapshot trié (getServers:112). Une instance d'Extension ne contient qu'un seul McpHub, mais le tableau connections: McpConnection[] compte un transport indépendant par entrée : si l'un tombe, les autres ne sont pas affectés.

Motivation de conception

  • Un client par serveur : les différents serveurs MCP ont des capabilities, des configurations et des gestions d'erreur différentes ; un client par serveur simplifie la reconnexion et l'isolation des périmètres (per-server client:363).
  • Trois transports unifiés : stdio pour un processus local, sse pour Server-Sent Events, streamableHttp pour le streaming HTTP de nouvelle génération, dispatchés dans le switch de connectToServer (transport switch:382).
  • Surveillance fichier + mise à jour interne en double canal : watchMcpSettingsFile détecte les éditions externes du fichier de configuration, et isUpdatingClineSettings marque quand c'est Cline lui-même qui écrit, pour skipper le déclenchement (isUpdatingClineSettings:76), évitant ainsi une boucle.
  • OAuth chargé à la demande : seuls sse/streamableHttp demandent un authProvider à McpOAuthManager, stdio n'y passe pas (authProvider setup:377).
  • Politique entreprise avant la connexion : les déploiements entreprise peuvent interdire le marketplace et poser une liste blanche, en bloquant avant même la connexion stdio (enterprise allowlist:310).
  • UID raccourci pour les noms d'outils : chaque serveur reçoit un court uid c + nanoid(5), pour éviter que les noms d'outils MCP ne forment de longues chaînes (getMcpServerKey:130).

Fichiers clés

  • McpHub class:51 — classe singleton, détient connections, le file watcher, l'OAuth manager et le callback de notification.
  • constructor:108 — au démarrage, lance immédiatement watchMcpSettingsFile + initializeMcpServers.
  • watchMcpSettingsFile:213 — via chokidar, surveille les modifications du fichier de configuration et diff les ajouts/suppressions/modifications pour déclencher une reconnexion.
  • connectToServer:286 — méthode de connexion principale : valide, choisit le transport, crée le client, découvre les capabilities.
  • transport switch:382 — trois branches stdio / sse / streamableHttp.
  • stdio stderr pipe:415 — pour les serveurs stdio, stderr est récupéré comme flux info/error de log.
  • fetchToolsList:677 — envoie tools/list et marque chaque tool avec autoApprove.
  • fetchResourcesList:713 — envoie resources/list ; les serveurs disabled renvoient vide.
  • notification handler:611 — enregistre le callback notifications/message et pousse les logs serveur vers le Task courant ou les met en attente.
  • deleteConnection:784 — ferme transport + client et filtre l'entrée de connections.
  • restartConnection:1047 — reconnexion d'un serveur avec un délai artificiel de 500 ms pour que l'UI voie l'état « connecting ».
  • callTool:1233 — envoie tools/call, prend le timeout dans la configuration serveur, et instrumente la télémétrie sur tout le parcours.

Flux de données

connectToServer commence par trois choses : valider la politique entreprise, construire un placeholder disabled ou un vrai client, et choisir le transport. Une fois le transport choisi, on crée le McpConnection, on le pousse dans le tableau, puis client.connect(transport) effectue le handshake. Ce n'est qu'après un handshake réussi qu'on enregistre le callback de notification et qu'on tire les quatre listes de capabilities :

typescript
// apps/vscode/src/services/mcp/McpHub.ts
connection.server.status = "connected"
connection.server.error = ""

// Register notification handler for real-time messages
try {
  // ...
  connection.client.setNotificationHandler(NotificationMessageSchema as any, async (notification: any) => {
    // ...
    if (this.notificationCallback) {
      this.notificationCallback(name, level, message)
    } else {
      this.pendingNotifications.push({ serverName: name, level, message, timestamp: Date.now() })
    }
  })
} catch (error) {
  Logger.error(`[MCP Debug] Error setting notification handlers for ${name}:`, error)
}

// Initial fetch of tools, resources, and prompts
connection.server.tools = await this.fetchToolsList(name)
connection.server.resources = await this.fetchResourcesList(name)
connection.server.resourceTemplates = await this.fetchResourceTemplatesList(name)
connection.server.prompts = await this.fetchPromptsList(name)

Ce bloc se trouve près de post-connect setup:584. Une fois la connexion établie, les appels d'outils externes passent tous par callTool : on cherche la connexion, on calcule le timeout selon config.timeout, on envoie tools/call (tools/call request:1270). La lecture de ressource est similaire, via resources/read (resources/read:1183).

Limites et échecs

  • OAuth absent = dégradation : quand client.connect lève UnauthorizedError, on ne propage pas l'erreur, on marque la connexion oauthRequired: true et on attend que l'utilisateur autorise côté UI (UnauthorizedError branch:556).
  • Serveur disabled = placeholder sans connexion : une config disabled entre quand même dans le tableau connections pour l'affichage UI, mais client/transport restent null (disabled placeholder:339).
  • Classification stdio stderr : si stderr contient le mot error, on log en error ; sinon on traite en info log (stderr classification:420).
  • streamableHttp : 404 traité comme 405 : beaucoup de serveurs renvoient 404 au lieu du 405 attendu quand SSE n'est pas supporté ; la couche fetch normalise le 404 en 405 pour que le SDK l'accepte (404 to 405:499).
  • Rafraîchissement dynamique du token SSE : la branche sse utilise un fetch personnalisé qui réobtient authProvider.tokens() à chaque requête, sans quoi un token expiré entraînerait une boucle de 401 à chaque reconnexion (dynamic token fetch:459).
  • La reconnexion streamableHttp est déléguée à un handler : StreamableHttpReconnectHandler reçoit la fermeture connectToServer et décide lui-même du rythme de reconnexion (reconnect handler:519).
  • Mise en attente des notifications : quand aucun Task n'est actif, les notifications serveur sont stockées dans pendingNotifications, et récupérées au démarrage du Task suivant (pendingNotifications:631).

Résumé

McpHub absorbe toute la complexité du protocole MCP et n'expose à l'agent principal que trois entrées : callTool / readResource / getServers. Pour voir comment la couche outils l'emballe en use_mcp_tool, lire cap-tools/use-mcp-tool ; pour voir comment le subagent isole sa fenêtre de contexte, lire subagent ; pour voir comment le Task principal injecte la liste des serveurs MCP dans le system prompt, lire agent-loop/task-class.

Voir la documentation officielle : Cline docs · README.