Skip to content

McpHub: gestión del ciclo de vida de servidores MCP

源码版本v4.0.10

Responsabilidades

McpHub es el núcleo central por el que Cline se conecta a servidores MCP externos. Se encarga de leer todos los servidores configurados en cline_mcp_settings.json, establecer para cada uno una conexión independiente de client + transport, y luego cachear en memoria las herramientas (tool), recursos (resource) y prompts (prompt) que exponen, de modo que el agente principal pueda invocarlos. Toda la cadena MCP —desde cambios de configuración, file watching, conexión/reconexión, descubrimiento de capacidades, hasta la llamada tools/call— converge en esta única clase; el Task principal no necesita tocar el SDK de MCP directamente.

Su posición se sitúa entre «el agent loop principal» y «el protocolo MCP concreto». Cuando el Task quiere invocar una herramienta MCP, lo hace a través de McpHub.callTool (callTool:1233); cuando la UI quiere consultar la lista actual de servidores, llama a getServers() y obtiene una instantánea ordenada (getServers:112). En cada instancia de Extension solo hay un McpHub, pero el arreglo connections: McpConnection[] contiene un transport independiente por entrada: si uno cae, no afecta a los demás.

Motivación de diseño

  • Un client por servidor: distintos servidores MCP tienen distintas capabilities, configuraciones y tratamientos de errores; un client por servidor simplifica la reconexión y el aislamiento de ámbito (per-server client:363).
  • Unificación de tres transport: stdio lanza procesos locales, sse usa Server-Sent Events y streamableHttp emplea el nuevo flujo HTTP; se reparten en el switch de connectToServer (transport switch:382).
  • Doble canal: file watcher + actualización interna: watchMcpSettingsFile detecta ediciones externas del archivo de configuración, mientras isUpdatingClineSettings marca cuando la escritura proviene del propio módulo y se salta el disparo (isUpdatingClineSettings:76), evitando bucles.
  • OAuth con carga perezosa: solo sse/streamableHttp piden un authProvider a McpOAuthManager; stdio no lo necesita (authProvider setup:377).
  • Políticas empresariales previas a la conexión: los despliegues empresariales pueden deshabilitar el marketplace o fijar listas blancas, interceptando conexiones stdio antes de que ocurran (enterprise allowlist:310).
  • UID corto para nombres de herramienta: cada servidor recibe un uid corto formado por c + nanoid(5), evitando que los nombres de herramientas MCP se conviertan en cadenas largas (getMcpServerKey:130).

Archivos clave

  • McpHub class:51 — clase singleton; mantiene connections, el file watcher, el OAuth manager y el callback de notificación.
  • constructor:108 — al iniciar lanza watchMcpSettingsFile + initializeMcpServers.
  • watchMcpSettingsFile:213 — usa chokidar para vigilar cambios en el archivo de configuración, calcula un diff de altas/bajas/modificaciones y dispara reconexiones.
  • connectToServer:286 — método de conexión central: valida, elige transport, crea el client y descubre las capacidades.
  • transport switch:382 — tres ramas: stdio / sse / streamableHttp.
  • stdio stderr pipe:415 — los servidores stdio canalizan stderr como flujo de logs info/error.
  • fetchToolsList:677 — envía tools/list y marca cada tool con autoApprove.
  • fetchResourcesList:713 — envía resources/list; los servidores deshabilitados devuelven vacío.
  • notification handler:611 — registra el callback notifications/message y entrega los logs del servidor al Task actual o los deja en stash.
  • deleteConnection:784 — cierra transport + client y filtra la conexión fuera del arreglo.
  • restartConnection:1047 — reconecta un servidor con un retardo artificial de 500 ms para que la UI muestre el estado «connecting».
  • callTool:1233 — envía tools/call aplicando el timeout por configuración del servidor y registrando telemetry en todo el recorrido.

Flujo de datos

connectToServer entra y hace tres cosas: valida políticas empresariales, construye un placeholder disabled o un client real, y elige transport. Tras elegir transport crea el McpConnection, lo empuja al arreglo y ejecuta client.connect(transport) para el handshake real. Solo después de un handshake exitoso registra los callbacks de notificación y obtiene las cuatro listas de capacidades:

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)

Esto se encuentra cerca de post-connect setup:584. Una vez establecida la conexión, las llamadas externas a herramientas pasan por callTool: busca la conexión, calcula el timeout según config.timeout, envía tools/call (tools/call request:1270). La lectura de recursos es análoga, vía resources/read (resources/read:1183).

Límites y fallos

  • OAuth ausente = degradación: si client.connect lanza UnauthorizedError, no lo reporta como error; marca la conexión como oauthRequired: true y espera a que el usuario autorice desde el frontend (UnauthorizedError branch:556).
  • Servidor deshabilitado como placeholder: la configuración deshabilitada sigue entrando en el arreglo connections para que la UI la muestre, pero client/transport quedan a null (disabled placeholder:339).
  • Clasificación de stderr en stdio: si stderr contiene la palabra error se registra como error; en caso contrario se trata como log info (stderr classification:420).
  • streamableHttp trata 404 como 405: muchos servidores devuelven 404 cuando deberían devolver 405 al no soportar SSE; la capa fetch normaliza 404 a 405 para que el SDK lo acepte (404 to 405:499).
  • Refresco dinámico de token SSE: la rama sse usa un fetch personalizado que obtiene authProvider.tokens() en cada llamada; de lo contrario, tras el vencimiento del token la reconexión seguiría devolviendo 401 (dynamic token fetch:459).
  • Reconexión streamableHttp delegada al handler: StreamableHttpReconnectHandler recibe el closure connectToServer y decide por sí mismo el ritmo de reconexión (reconnect handler:519).
  • Notificaciones en stash: cuando no hay un Task activo, las notificaciones entrantes del servidor se guardan en pendingNotifications hasta que se levante el siguiente Task (pendingNotifications:631).

Resumen

McpHub encapsula todos los detalles del protocolo MCP y expone al agente principal tres entradas: callTool/readResource/getServers. Para ver cómo la capa de herramientas lo envuelve como use_mcp_tool, consulta cap-tools/use-mcp-tool; para ver cómo un subagente aísla su propia ventana de contexto, consulta subagente (subagent); para ver cómo el Task principal inyecta la lista de servidores MCP en el system prompt, consulta agent-loop/task-class.

Véase la documentación oficial: Cline 文档 · README