Skip to content

Escalado y alta disponibilidad

MCP REST Bridge corre feliz como un único proceso — una instancia Bun con un fichero SQLite local maneja mucho. Cuando necesitas redundancia o más throughput, escala horizontalmente: ejecuta varias instancias idénticas tras un load balancer, coordinadas a través de una base de datos SQLite compartida.

El modelo

  • Manejo de requests stateless. Cada llamada de tool es autocontenida; cualquier instancia puede servir cualquier llamada REST.
  • SQLite es la capa de coordinación. Config admin, guards, keys, audit, uso y las primitivas HA viven todas en la base de datos. Apunta cada instancia al mismoDB_PATH (almacenamiento compartido / un volumen compartido) para que vean una sola config.
  • Flags de HA opt-in activan comportamiento cross-instancia (abajo) — están off por defecto para que un único nodo se mantenga simple.
Scaling MCP REST Bridge horizontally MCP clients reach a load balancer, which spreads traffic across several identical bridge instances. Every instance shares one SQLite database for config, cross-instance rate counters, registry sync and the leader lease. MCP clientsLoad balancerhealth-check /health · sticky MCP sessionsMCP REST Bridgeinstance 1MCP REST Bridgeinstance 2MCP REST Bridgeinstance 3Shared SQLiteconfig · rate counters · registry sync · leader lease
Identical instances behind a load balancer, coordinated through one shared SQLite. Each still proxies to your REST & MCP backends; background loops run on the elected leader only.

Activar las primitivas HA

SettingEfecto
RATE_LIMIT_SHARED=trueLos rate limits usan contadores fixed-window en SQLite, de modo que un límite por tool se aplica en todas las instancias, no por proceso.
REGISTRY_SYNC=trueCada instancia reconcilia periódicamente su registry en vivo desde SQLite — un cliente registrado (o eliminado) en un nodo se propaga a los otros.

Los loops en background que deben correr una vez — evaluación de alertas, schedules de mantenimiento y el loop de health-check/auto-eliminación — eligen un único líder automáticamente vía un lease en SQLite. Esto no es una flag; siempre está activo y no requiere configuración.

Load balancing de tus backends

Separado de escalar el bridge mismo, un único cliente puede fan-out entre varios targets de backend (load balancing N-way), configurado por cliente desde la admin API. Un target que falla se salta durante LB_TARGET_COOLDOWN_MS (por defecto 30s) antes de probarlo de nuevo. Combina con canary/failover por cliente (consulta Guardrails y resiliencia) para degradación con gracia.

Sesiones MCP y sticky routing

El transporte Streamable HTTP mantiene estado por sesión en memoria en la instancia que abrió la sesión. Dos opciones:

  • Sticky sessions — habilita afinidad de sesión en tu load balancer para los endpoints MCP (/mcp, /mcp/:name, /mcp-custom/:bundle) para que una sesión se quede en una instancia. Recomendado para clientes streaming.
  • Llamadas stateless — los clientes que abren un request fresco por llamada no necesitan afinidad y se balancean libremente.

El proxy REST y la admin API no necesitan afinidad.

Caveats que debes conocer

  • SQLite compartido requiere almacenamiento compartido. SQLite sobre un filesystem de red tiene quirks de locking; prefiere un volumen que todas las instancias monten localmente, o mantén las escrituras modestas. Para volumen de escritura muy alto, ejecuta menos instancias, más grandes.
  • La cadena de hash del audit es por instancia. Su tamper-evidence (verifyAuditChain) asume un único escritor; la integridad de la cadena cross-instancia está fuera de scope — streamea a un SIEM (AUDIT_SINK_URL) para un registro consolidado y ordenado en su lugar.
  • Un health-eviction hold también es por instancia, si enrutas con /livez en vez de /readyz. Bajo la topología por defecto (solo-líder, gateada por /readyz) esto no importa — solo el líder llega a probar backends o servir tráfico. Pero si cambias el readiness a /livez para que cada réplica sirva (consulta el override del readiness-probe del Helm chart →), un re-registro de cliente puede aterrizar en una réplica que no es el líder y solo limpiar la marca de eviction en memoria de esa réplica, dejando la propia marca del líder (y por tanto su reconcileFromDb()) atascada reteniendo el cliente. Vuelve a registrar contra el líder, o usa forgetClient/re-regístralo a través de una sesión anclada a él, para limpiar el hold.

Checklist

  • [ ] Todas las instancias comparten un DB_PATH
  • [ ] RATE_LIMIT_SHARED=true y REGISTRY_SYNC=true
  • [ ] El load balancer chequea /health (o /livez) para enrutar tráfico REST/MCP del plano de datos — el manejo de requests es stateless, así que cualquier instancia sirve sin importar el liderazgo. /readyz da 200 solo en el líder actual (leader lease + base de datos); apuntar ahí un LB de escalado por throughput saca de rotación a todas las demás instancias. Reserva el enrutado condicionado a /readyz para una topología deliberada de failover activo/pasivo, y usa /livez para el probe de liveness/reinicio del orquestador
  • [ ] Sticky sessions para los endpoints MCP /mcp · /mcp/:name · /mcp-custom/:bundle (si usas streaming)
  • [ ] AUDIT_SINK_URL configurado para un audit trail consolidado

Consulta Despliegue → para el setup del contenedor y Configuración → para cada flag.

Distribuido bajo la licencia MIT · Construido con Bun + Vue.