Skip to content

McpHub: Lebenszyklusverwaltung der MCP-Server

源码版本v4.0.10

Verantwortung

McpHub ist die zentrale Anlaufstelle von Cline für die Anbindung externer MCP-Server. Er liest alle in cline_mcp_settings.json konfigurierten Server, baut für jeden Server eine unabhängige Client + Transport-Verbindung auf und cached die exponierten Werkzeuge (tool), Ressourcen (resource) und Prompts im Speicher, damit der Haupt-Agent sie aufrufen kann. Die gesamte MCP-Werkzeugkette — von Konfigurationsänderungen, Datei-Überwachung, Verbindung/Wiederverbindung, Capability-Discovery bis zum tools/call-Aufruf — ist in dieser einen Klasse gebündelt, der Haupt-Task muss das MCP-SDK nicht direkt anfassen.

Seine Position liegt zwischen der «Haupt-Agent-Loop» und dem «konkreten MCP-Protokoll». Wenn ein Task ein MCP-Werkzeug aufrufen möchte, geht er über McpHub.callTool (callTool:1233) hinaus; wenn die UI die aktuelle Server-Liste sehen will, holt sie über getServers() eine sortierte Momentaufnahme (getServers:112). In einer Extension-Instanz existiert nur ein McpHub, aber im Array connections: McpConnection[] ist jeder Eintrag ein unabhängiger Transport — fällt einer aus, beeinflusst das die anderen nicht.

Entwurfsmotivation

  • Ein Client pro Server: Verschiedene MCP-Server haben unterschiedliche Capabilities, Konfigurationen und Fehlerbehandlungen; ein unabhängiger Client macht Wiederverbindung und Scoping-Isolation einfach (per-server client:363).
  • Vereinheitlichung von drei Transporten: stdio startet lokale Prozesse, sse nutzt Server-Sent Events, streamableHttp verwendet den neuen HTTP-Strom; verteilt im switch von connectToServer (transport switch:382).
  • Datei-Überwachung + interner Update-Dual-Kanal: watchMcpSettingsFile überwacht externe Editor-Änderungen an der Konfiguration, isUpdatingClineSettings markiert interne Schreibvorgänge und überspringt dabei das Auslösen (isUpdatingClineSettings:76), um Schleifen zu vermeiden.
  • OAuth bedarfsgerecht lazy geladen: Nur sse/streamableHttp holen sich von McpOAuthManager den authProvider, stdio nicht (authProvider setup:377).
  • Enterprise-Richtlinien vor der Verbindung: Enterprise-Deployments können den Marketplace deaktivieren und Whitelists setzen, die vor der stdio-Verbindung abgefangen werden (enterprise allowlist:310).
  • UID verkürzt Werkzeugnamen: Jedem Server wird eine kurze UID aus c + nanoid(5) zugewiesen, um zu verhindern, dass MCP-Werkzeugnamen zu langen Zeichenketten zusammengefügt werden (getMcpServerKey:130).

Schlüsseldateien

  • McpHub class:51 — Singleton-Klasse, hält connections, Datei-Watcher, OAuth-Manager und Notification-Callbacks.
  • constructor:108 — startet sofort watchMcpSettingsFile + initializeMcpServers.
  • watchMcpSettingsFile:213 — überwacht mit chokidar Änderungen an der Konfigurationsdatei, diffed Hinzufügen/Löschen/Ändern und löst Wiederverbindung aus.
  • connectToServer:286 — zentrale Verbindungsmethode: validiert, wählt Transport, erstellt Client, entdeckt Capabilities.
  • transport switch:382 — Drei Zweige: stdio / sse / streamableHttp.
  • stdio stderr pipe:415 — stdio-Server leiten stderr als info/error-Logstrom aus.
  • fetchToolsList:677 — sendet tools/list und markiert jedes Werkzeug mit autoApprove.
  • fetchResourcesList:713 — sendet resources/list, deaktivierte Server liefern leer.
  • notification handler:611 — registriert den notifications/message-Callback und leitet Server-Logs an den aktuellen Task oder puffert sie.
  • deleteConnection:784 — schließt Transport + Client, filtert aus connections heraus.
  • restartConnection:1047 — verbindet einen Server neu, mit 500 ms künstlicher Verzögerung, damit die UI den Status «connecting» sieht.
  • callTool:1233 — sendet tools/call, ermittelt den Timeout anhand der Server-Konfiguration, überall mit Telemetry.

Datenfluss

connectToServer macht beim Eintritt drei Dinge: Enterprise-Richtlinie validieren, deaktivierten Platzhalter oder echten Client konstruieren, Transport wählen. Nach der Transportauswahl wird ein McpConnection erstellt und ins Array gepusht, dann client.connect(transport) für den echten Handshake. Nach erfolgreichem Handshake werden Notification-Callbacks registriert und vier Capability-Listen gezogen:

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)

Dieses Stück liegt nahe post-connect setup:584. Nach dem Verbindungsaufbau laufen alle externen Werkzeugaufrufe über callTool — Verbindung nachschlagen, Timeout anhand config.timeout berechnen, tools/call senden (tools/call request:1270). Ressourcen-Lesen erfolgt analog über resources/read (resources/read:1183).

Grenzen und Fehler

  • OAuth-Fehlen bedeutet Degradierung: Wenn client.connect eine UnauthorizedError wirft, wird nicht als Fehler gemeldet, sondern die Verbindung als oauthRequired: true markiert, bis der Benutzer im Frontend autorisiert (UnauthorizedError branch:556).
  • Deaktivierte Server werden als Platzhalter gehalten, aber nicht verbunden: Deaktivierte Konfigurationen gelangen in das connections-Array, damit die UI sie anzeigen kann, aber client/transport sind beide null (disabled placeholder:339).
  • stdio-stderr-Klassifizierung: In stderr wird nur dann ein error protokolliert, wenn das Wort error auftaucht, sonst als info-Log behandelt (stderr classification:420).
  • streamableHttp 404 als 405: Viele Server geben bei nicht unterstütztem SSE fälschlich 404 statt 405 zurück; hier wird in der Fetch-Schicht 404 zu 405 normalisiert, damit das SDK es akzeptiert (404 to 405:499).
  • SSE-Token dynamisch refreshen: Der sse-Zweig nutzt einen benutzerdefinierten fetch, der jedes Mal authProvider.tokens() frisch holt, da sonst nach Token-Ablauf die Wiederverbindung dauerhaft 401 liefert (dynamic token fetch:459).
  • streamableHttp-Wiederverbindung an Handler delegiert: StreamableHttpReconnectHandler erhält den connectToServer-Closure und entscheidet selbst über das Tempo der Wiederverbindung (reconnect handler:519).
  • Notification-Puffer: Wenn kein aktiver Task aktiv ist, werden Server-Notifications zuerst in pendingNotifications gespeichert und beim Start des nächsten Tasks abgerufen (pendingNotifications:631).

Zusammenfassung

McpHub kapselt alle MCP-Protokolldetails in sich selbst und exponiert dem Haupt-Agent drei Öffnungen: callTool/readResource/getServers. Um zu sehen, wie es vom Tool-Layer in use_mcp_tool gewrappt wird, lies cap-tools/use-mcp-tool; um zu sehen, wie ein Subagent sein eigenes Kontextfenster isoliert, lies subagent; um zu sehen, wie der Haupt-Task die MCP-Server-Liste in den Systemprompt einbaut, lies agent-loop/task-class.

Siehe offizielle Dokumentation: Cline-Dokumentation · README