Por qué tus herramientas MCP te desconectan sin parar: dentro del ciclo de vida del token OAuth


El mes pasado conectaste Linear, Figma y Notion a Hermes. hermes mcp list mostraba tres servidores sanos y cada llamada a herramienta funcionaba sin más. Esta mañana todas las herramientas MCP responden con un error de autorización — Desktop lanzó un aviso anoche, estabas en una reunión y lo descartaste. Vuelves a autenticar Linear y para la tarde Figma también se ha caído. El patrón se repite casi cada semana siempre que Hermes corre a la vez en tu portátil y en la máquina de la oficina, y empiezas a preguntarte qué configuraste mal. Probablemente nada: el fallo está en el largo ciclo de vida del token que hay detrás de esas conexiones, y Hermes acaba de publicar cuatro arreglos dirigidos justo a eso.

El handshake que hay detrás de esas herramientas: access tokens, refresh tokens y “usar una sola vez”

MCP (Model Context Protocol) es el protocolo que permite a Hermes llamar a herramientas que expone un servicio externo; Linear, Figma y Notion no aceptan una API key estática, entregan credenciales mediante OAuth. Cuando terminas un login OAuth obtienes dos cosas:

  • Un access token — de vida corta, normalmente entre treinta y sesenta minutos. Viaja con cada llamada a herramienta.
  • Un refresh token — de vida larga. Su única misión es conseguirte un access token nuevo.

Para que un refresh token robado deje de ser útil enseguida, los servidores de autorización modernos activan la rotación: cada vez que cambias tu refresh token por un access token nuevo, el refresh token antiguo se invalida en el acto y se emite uno nuevo. Un diseño sensato — y viene con una red de seguridad llamada detección de reutilización (reuse detection): si el mismo refresh token aparece por segunda vez, el servidor no puede distinguir un reintento de red inestable de una credencial robada, así que toma la vía segura y revoca la grant entera (todos los tokens de ese cliente y ese usuario), y luego te pide que inicies sesión otra vez.

Ahí es donde una instalación normal se topa con problemas. Desktop, el dashboard y un gateway en segundo plano pueden estar corriendo en la misma máquina, o tienes dos profiles, y comparten el mismo archivo de tokens en ~/.hermes/mcp-tokens/linear.json. Cuando el access token caduca, dos procesos se dan cuenta más o menos en el mismo instante y cada uno presenta el mismo refresh token:

  1. El proceso A llega primero, recibe un par nuevo y el refresh token antiguo queda anulado.
  2. El proceso B va unos cientos de milisegundos por detrás y presenta ese refresh token ya anulado.
  3. El servidor lo interpreta como un replay y revoca toda la grant — incluidos los tokens sanos y recién emitidos que el proceso A acababa de recibir.

Los dos procesos se quedan a oscuras, por un motivo que desde fuera parece un sinsentido: hace un segundo funcionaba. Cerrar esa ventana es de lo que va el PR #111186.

Arreglo uno: una valla entre procesos para que un refresh token rotado se presente exactamente una vez

Hermes coloca ahora un lock entre procesos junto al archivo de tokens — una valla. El lock viene del kernel (flock en Linux y macOS, msvcrt.locking en Windows) y bloquea un handle de archivo, no la existencia de un archivo, así que un proceso caído que deje atrás un archivo de lock huérfano no puede engañarlo. El lock vive en un archivo hermano con un nombre tipo linear.json.refresh.lock.

Toda la secuencia — leer el refresh token, hacer POST al servidor de autorización, escribir el resultado de vuelta a disco — ocurre ahora dentro de ese lock, lo que cambia el comportamiento a:

  • Releer el disco una vez tomado el lock. Si un proceso par completó una rotación mientras esperábamos y el par que escribió sigue vivo, este proceso simplemente adopta los tokens frescos del par y se salta el POST, en lugar de presentar un token que ya se ha gastado.
  • Si el par del proceso par ya ha caducado, este proceso hace POST con el refresh token del par — que es el único válido en ese momento — en lugar de la copia obsoleta que tiene en su propia memoria.
  • Una caducidad ausente cuenta como vivo. Cuando el servidor no declara una vida útil, Hermes no asume que el token esté muerto.
  • Sin lock, no hay refresh. Si la valla agota el tiempo de espera, el refresh falla en cerrado en vez de apostar por un replay. Solo los códigos de error de contención significan “un par tiene la valla”; cualquier otra cosa falla rápido en lugar de esperar.

Qué significa para ti: correr Desktop, el dashboard y un gateway a la vez es seguro, y también lo es tener Linear conectado en dos profiles. Las desconexiones dejan de ser aleatorias e inexplicables — un refresh o funciona, o falla con un error claro.

Arreglo dos: los refresh tokens quedan anclados al emisor que los concedió

El siguiente peligro es más silencioso. Un servidor de autorización OAuth anuncia su propia dirección de identidad en sus metadatos; ese valor es el issuer. Hermes descubre esos metadatos desde la URL que publica el servidor MCP — y si lo que hay detrás de esa URL cambia de carácter (una migración de servicio, alguien editando los metadatos o, peor, un dominio secuestrado), Hermes entregaría un refresh token de larga vida a un servidor que nunca lo emitió. Un refresh token vale mucho más que un access token; esa entrega es una credencial de largo plazo puesta en manos de un desconocido.

El arreglo: Hermes registra un campo hermes_issuer junto a los tokens en caché, anotando quién los concedió originalmente. Ese campo es contabilidad interna de Hermes — se elimina antes de que el SDK valide el token y nunca sale por la red. Cada vez que inicializa una conexión, Hermes compara el issuer guardado con el que descubre en ese momento:

  • No coinciden → el refresh token se descarta, en memoria y en disco, mientras que el access token aún válido se conserva, porque sigue siendo válido para el servidor que lo emitió. Sigue funcionando hasta que caduca, y entonces se ejecuta una reautorización normal contra el nuevo issuer.
  • Coinciden → no pasa nada, los tokens guardados siguen funcionando.
  • Archivos de token antiguos sin issuer registrado (cualquier instalación anterior a este campo) → el issuer descubierto actual se adopta una vez y se registra, en lugar de forzarte a un re-login. Es una decisión deliberada: Hermes tiene muchísimas instalaciones cuyos archivos de token son anteriores a este campo, y obligarlas a todas a reautenticarse sería puro fastidio.

Merece la pena señalar: esto se aparta a propósito de un cambio hermano en codex (openai/codex), que falla de forma dura cuando falta el issuer porque su almacén de credenciales incluyó el campo desde el primer día.

Arreglo tres: RFC 9207 y la única excepción de Figma

RFC 9207 es una extensión de OAuth: el servidor de autorización añade un parámetro iss cuando redirige el navegador de vuelta al cliente, declarando “soy quien digo ser”. Con él, el cliente puede confirmar que el callback viene de verdad del servidor de autorización en el que confía, y no de un authorization code inyectado por otro — es una defensa contra los ataques de confusión de servidor de autorización (mix-up).

Aquí había dos modos de fallo opuestos, y Hermes arregló uno de cada:

Los relays perdían el iss. Cuando un servidor anuncia soporte de ese parámetro en sus metadatos y luego lo omite, el SDK subyacente rechaza el callback con OAuthFlowError: Authorization response missing iss parameter. La ruta de la CLI siempre lo pasaba, pero los relays de callback del dashboard, Desktop y TUI no llevaban iss hasta el cliente MCP — así que contra un servidor realmente conforme con RFC 9207 como Cloudflare o Resend, iniciar sesión desde las interfaces gráficas era un callejón sin salida. Ahora todos los relays pasan el parámetro.

Figma anuncia lo que no envía. El servidor de autorización de Figma (https://api.figma.com) declara soportar iss y nunca lo incluye, así que el SDK descartaba todos los authorization codes válidos y hermes mcp login figma no podía completarse nunca. Hermes abre un hueco para exactamente ese issuer: un callback que trae un code pero no un iss recibe el valor que falta rellenado desde el issuer de los metadatos descubiertos, con un warning registrado en el log. Un iss presente pero distinto sigue rechazándose, y todos los demás servidores mantienen la regla estricta. No hay ningún toggle ni ninguna variable de entorno nueva.

Arreglo cuatro: un recordatorio de reautenticación una vez al día en Desktop

Cuando un token muere, el servidor queda aparcado y deja de intentar abrir una pestaña del navegador — no hay nadie ahí para pulsar Allow. Eso está bien, salvo que hacía que un token muerto fuera invisible: el health check lanzaba un aviso en el momento en que el estado cambiaba y luego se quedaba callado el resto de la sesión de la app. Si descartas ese aviso un lunes, el servidor se queda aparcado hasta que por casualidad abras la página Capabilities.

Desktop ahora avisa una vez al día en su lugar: en la transición a un estado malo, y otra vez cuando ha transcurrido el snooze de 24 horas si el servidor sigue roto. Nunca avisa mientras los tokens están sanos. El aviso lleva dos botones:

  • Sign in — lleva al botón de autenticación que ya existe en la página de MCP. Nada lanza un flujo OAuth en segundo plano por su cuenta.
  • Disable — llama al endpoint existente para poner enabled: false en ese servidor. La tarea de reconciliación de config del gateway lo desmonta en unos 60 segundos, y un backend hermes serve lo quita en el siguiente /reload-mcp.

El snooze se registra por profile y por servidor, así que un servidor roto no puede darte la lata una docena de veces al día, y un servidor que ya has reconocido no se queda en silencio para siempre.

Qué arregló cada PR

PR Etiqueta Cambio de comportamiento
#95246 type/security (2026-09-14) Los refresh tokens guardados ya no pueden enviarse a un issuer distinto: si no coinciden, el refresh token se descarta y el access token no caducado se conserva; los archivos de token antiguos adoptan el issuer descubierto una vez
#111186 type/bug, P1 (2026-09-15) El refresh queda vallado entre procesos: un refresh token rotado se presenta exactamente una vez, el par fresco de un proceso par se adopta saltándose el POST, y un timeout de la valla falla en cerrado
#111628 type/bug, P2 (2026-09-15) El login OAuth se completa contra servidores RFC 9207 desde el dashboard, Desktop y TUI (Cloudflare, Resend)
#112059 type/bug, P2 (2026-09-15) El login de Figma se completa a pesar del parámetro iss omitido; la regla estricta se relaja solo para ese issuer
#110078 type/feature (2026-09-13) Desktop avisa una vez al día para los servidores MCP que necesitan reautenticación, con Sign in y Disable
#111481 fix (2026-09-15) Los profiles servidos por el dashboard conservan sus propias conexiones y credenciales MCP en lugar de tomar prestada la sesión de otro profile

Qué hacer cuando un servidor MCP te pide iniciar sesión otra vez

Repasa estos pasos en orden; la mayoría de los casos se resuelven en uno o dos pasos.

  1. Lee el mensaje antes de reconstruir nada. “Refresh failed” y “the issuer changed” son clases de problema distintas — el primero es un token caducado o revocado, el segundo significa que el servidor de autorización de ese lado se ha movido.
  2. Localiza el servidor afectado. hermes mcp list muestra qué servidores están aparcados, y hermes mcp test linear sondea una sola conexión.
  3. Reautoriza una vez. hermes mcp login linear para un servidor, hermes mcp reauth --all para rehacer todos los servidores OAuth. En Desktop, basta con pulsar Sign in en el aviso.
  4. Deja que la sesión vea los tokens nuevos. El conjunto de herramientas de una sesión queda congelado cuando arranca, así que usa /reload-mcp después de reautorizar, o abre una sesión nueva.
  5. Actualiza las dos máquinas. La valla es cooperativa — necesita que todos los procesos participen. Actualiza un host y el otro seguirá repitiendo tokens a la vieja usanza, así que asegúrate de que ambos incluyan el PR #111186.
  6. Recuerda que un gateway nunca abre un navegador. Si corres un gateway en un servidor, un token muerto solo deja un warning en el log; completa el flujo en otro sitio y se reconectará en el siguiente sondeo.
  7. Si dudas, empieza de cero. Los archivos de token viven en ~/.hermes/mcp-tokens/, uno por nombre de servidor. Borrar el correcto y volver a ejecutar hermes mcp login es el reset más limpio que hay disponible.
  8. Desactiva lo que ya no usas. Nadie necesita un recordatorio diario sobre una integración que abandonaste el trimestre pasado.

Si Hermes vive en un host remoto sin navegador, el flujo es distinto — el port forwarding por SSH, pegar de vuelta la callback URL y los device codes están cubiertos en completar OAuth en un servidor remoto. Para los flags exactos de cada subcomando, consulta la referencia del comando hermes mcp, y si lo que estás decidiendo es qué servidores conectar de entrada, el catálogo oficial de MCP remotos es mejor punto de partida que unos resultados de búsqueda al azar.

La versión corta

Dos de los cuatro arreglos son el mismo principio visto desde ángulos distintos: una credencial solo va al servidor que la emitió (vinculación al issuer) y un refresh token rotado se consume exactamente una vez (la valla entre procesos). El tercero impone la identidad del llamante en todas las rutas por las que puede viajar un callback (RFC 9207, con la única excepción de Figma), y el cuarto es un parche humano — cuando algo necesita tu atención, dilo, una vez al día, con un botón que lo resuelve.

En la práctica, eso significa que las caídas de MCP dejan de parecer mala suerte. Corre Desktop, el dashboard y un gateway al mismo tiempo; cuando un servidor te necesite de verdad, lo sabrás ese mismo día en lugar de descubrir que todas las herramientas están rotas desde el martes. hermes update incorpora todo esto — los arreglos están en la línea 0.21.x, y todo lo posterior al parche de estabilidad v0.21.2 ya los incluye.