Hermes MCP include: una lista vacía ya bloquea todo, en todas partes


Conectaste un servidor MCP de sistema de archivos al Hermes Agent de tu equipo y, para ir sobre seguro, desmarcaste todas las herramientas que trae de serie: ya las irías activando de una en una si alguien las necesitaba de verdad. En la configuración aparece tools.include: [], y diste por hecho que el asunto estaba resuelto. Hasta que una tarde hermes mcp list imprime all junto a ese servidor y el agente sigue leyendo y escribiendo en esos directorios. La lista vacía no se estaba leyendo como un bloqueo total, sino como «esta clave no está definida», es decir, «sin filtro», es decir, todo habilitado.

Una frase de contexto: MCP (Model Context Protocol) es el protocolo que permite a Hermes llamar a herramientas servidas por servidores externos, y tools.include es la lista blanca de herramientas de ese servidor: solo se pueden llamar las que aparecen en ella.

Tres estados posibles y solo uno bloquea todo

En realidad solo hay tres estados, y sus significados no tienen nada que ver entre sí:

  • Ausente: no hay bloque tools, o hay un bloque tools pero sin la clave include. Esto significa «sin filtro»: todas las herramientas que expone el servidor quedan habilitadas.
  • Parcialmente relleno: include: [read_file, list_directory]. Una lista blanca de verdad: solo esas herramientas se pueden llamar.
  • Explícitamente vacío: include: []. Esto es un bloqueo total: no se registra nada.

El problema es que los dos últimos casos se colapsan en la misma rama de código con una facilidad pasmosa. Una lista vacía es falsy en Python, así que en cuanto escribes cfg.get("include") or [], «el usuario escribió una lista vacía» y «el usuario no escribió nada» se vuelven indistinguibles, y el segundo caso es justo lo contrario de lo que pretendía el usuario.

Conviene subrayar que el runtime siempre estuvo en lo correcto. tools/mcp_tool_registration.py no registra nada cuando ve include: [], así que ni una sola herramienta llega al agente. Tu bloqueo sí estaba en vigor. Era la interfaz la que te mentía.

La lista vacía desapareció en la mitad de la interfaz

Puesto que el runtime se comportaba bien, el fallo tenía que estar en los lectores. Varias interfaces leían include con or [] o con una comprobación de veracidad a secas, de modo que una lista vacía y una clave ausente se trataban como lo mismo y se mostraban como «todas las herramientas habilitadas». El PR #109188 (fusionado en main el 2026-09-12) corrige a los lectores que se habían desviado. Su título es «fix(mcp): include: [] no longer reopens as “all tools enabled” in CLI, catalog and desktop»; cierra la issue #12865 y el diff es de +107 / −31 en 8 archivos.

Una tabla deja la divergencia a la vista (los valores «antes» y «después» se refieren a antes y después del PR #109188):

Punto de comprobación Antes Después
hermes mcp list all 0 selected
fila de lista de hermes tools all tools enabled [include only: (none)]
selector de hermes tools todas las herramientas premarcadas; al confirmar se reactivaban todas nada premarcado; al confirmar se mantiene include: []
runtime _make_tool_filter("demo")("a") False False (sin cambios)

Merece la pena recordar cuatro cambios:

  • _tool_filters en hermes_cli/mcp_config.py: si la clave contiene una lista, devuelve esa lista (lista vacía incluida); solo devuelve None cuando la clave falta o no es una lista. La salida de listado y el selector de configure ahora comprueban is not None en lugar de la veracidad.
  • hermes_cli/tools_config_mcp.py: el estado premarcado del selector de hermes tools y la fila de lista, que ahora muestra [include only: (none)].
  • hermes_cli/mcp_catalog.py: la comprobación previa de una reinstalación interactiva respeta un [] anterior en lugar de volver al valor por defecto del catálogo.
  • apps/desktop/src/lib/mcp-tool-filter.ts: isToolEnabled trata «existe include» como lista blanca; lo vemos en la sección siguiente.

El comentario del código es tajante:

Un include: [] explícito es una lista blanca real (bloqueo total); el runtime no registra ninguna herramienta, así que no debe colapsarse aquí en «sin filtro».

Un detalle más que conviene decir con claridad: el lado del runtime ya se había corregido en otro commit (fb1ec36a4b86). Este PR va de los lectores que seguían divergiendo de él. No es una función nueva: es realinear todas las superficies con una semántica que el runtime ya había fijado.

La trampa más invisible del escritorio: borrar la clave y recuperarlo todo

En el panel MCP de escritorio, isToolEnabled en apps/desktop/src/lib/mcp-tool-filter.ts ahora entiende «include está presente» como lista blanca. Parece un cambio menor, pero cierra una trampa peor que cualquiera de las del CLI: al desactivar la última herramienta incluida, conserva include: [] en lugar de borrar la clave.

¿Por qué es importante? Porque a nivel de archivo de configuración una lista vacía y una clave ausente no son ni de lejos lo mismo:

  • Conservar include: [] = bloqueo total explícito; no se registra nada.
  • Borrar la clave include = sin filtro; el servidor se reabre por completo y en silencio.

En la interfaz las dos situaciones se ven idénticas: simplemente no hay ninguna casilla marcada. Borrar la clave parece una limpieza inofensiva, pero en realidad arranca el bloqueo de raíz. Si tu equipo tiene scripts o costumbres que «limpian campos vacíos», ese es exactamente el patrón que hay que vigilar.

Sobre la procedencia: la parte de escritorio se trajo por cherry-pick desde #52874 (@Bartok9, a su vez un rescate de #13096 de @dingn42).

Cómo bloquear de verdad un servidor MCP

Escribe la intención explícitamente en el YAML en lugar de fiarte de lo que la interfaz ponga por defecto:

mcp_servers:
  demo:
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/srv/data"]
    tools:
      include: []        # bloqueo total explícito: no registra ninguna herramienta
  demo2: {}              # sin clave tools = sin filtro, todo habilitado

Después verifícalo, en este orden:

  1. Ejecuta hermes mcp list. Junto a demo deberías ver 0 selected, no all.
  2. Ejecuta hermes tools. Esa fila debería mostrar [include only: (none)]; abre el selector y no debería haber ninguna herramienta premarcada, y confirmar directamente no debería activar nada.
  3. Cuando más adelante quieras una herramienta concreta, añade su nombre a la lista include; no borres la clave.

Si vienes de una versión antigua, merece la pena auditar también la configuración existente: para cualquier servidor que esté en el estado «quería bloquearlo todo y nadie lo ha tocado desde entonces», comprueba si de verdad tiene include: [] o simplemente no tiene include en absoluto. Se parecen casi exactamente y hacen lo contrario.

Para seguir leyendo: si quieres usar variables de contexto dentro de la configuración de MCP, mira Hermes MCP config context variables; para entender el perímetro de seguridad de la superficie de herramientas MCP, consulta la Hermes MCP catalog security guide; si todavía estás eligiendo qué servidores conectar, el Hermes official remote MCP catalog es un buen punto de partida; y si aún no has instalado Hermes, la guía de instalación cubre los requisitos y el primer paso.

Resumen

  • tools.include: [] nunca fue «sin configurar». Es un bloqueo total. El runtime siempre lo leyó así; varias superficies lo leían mal.
  • El PR #109188 (fusionado en main el 2026-09-12) alinea hermes mcp list, hermes mcp configure, hermes tools, la reinstalación del catálogo y el panel MCP de escritorio con el runtime: una lista vacía explícita significa 0 seleccionados.
  • La trampa que más duele es borrar la clave: include: [] y no tener include se ven igual en la interfaz y significan lo contrario.
  • Verificarlo es barato: si hermes mcp list dice 0 selected, tu bloqueo se sostiene.