McpHub: Lebenszyklusverwaltung der MCP-Server
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:
stdiostartet lokale Prozesse,ssenutzt Server-Sent Events,streamableHttpverwendet den neuen HTTP-Strom; verteilt imswitchvonconnectToServer(transport switch:382). - Datei-Überwachung + interner Update-Dual-Kanal:
watchMcpSettingsFileüberwacht externe Editor-Änderungen an der Konfiguration,isUpdatingClineSettingsmarkiert interne Schreibvorgänge und überspringt dabei das Auslösen (isUpdatingClineSettings:76), um Schleifen zu vermeiden. - OAuth bedarfsgerecht lazy geladen: Nur
sse/streamableHttpholen sich vonMcpOAuthManagerden authProvider,stdionicht (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 sofortwatchMcpSettingsFile+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— sendettools/listund markiert jedes Werkzeug mitautoApprove.fetchResourcesList:713— sendetresources/list, deaktivierte Server liefern leer.notification handler:611— registriert dennotifications/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— sendettools/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:
// 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.connecteineUnauthorizedErrorwirft, wird nicht als Fehler gemeldet, sondern die Verbindung alsoauthRequired: truemarkiert, 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
errorauftaucht, 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:
StreamableHttpReconnectHandlererhält denconnectToServer-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
pendingNotificationsgespeichert 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