MCP Fusion/Protocol and runtime/Sincronización de estado

Sincronización de estado

Pregunta a la IA sobre Vinkius

Indica al agente qué se puede reutilizar y qué acaba de quedar obsoleto: pistas de cache-control en las descripciones, invalidación con alcance glob en las mutations, notificaciones de recursos y caché de listas de MCP 2.0.

El fallo clásico de un agente después de una mutation: vuelve a leer datos obsoletos, "arregla" el problema equivocado o reaplica su cambio porque la lectura vino de su propio contexto, no de tu servidor. La capa de sincronización de estado de MCP Fusion responde a una sola pregunta: ¿cuándo puede el agente confiar en datos en caché, y qué le avisa de que un dominio acaba de quedar obsoleto?

Esto no es una caché de respuestas. MCP Fusion nunca almacena resultados de tools. Es un sistema de señalización temporal, que usa vocabulario de caché HTTP que el modelo ya entiende.

Declarando la política

Tres métodos en cualquier tool:

typescript
f.query('billing.get_invoice')
  .cached()                          // "[Cache-Control: immutable]" on the description
  .handle(...)

f.mutation('billing.refund')
  .invalidates('billing.get_*', 'dashboard.invoices')
  .handle(...)
  • .cached() estampa immutable; .stale() estampa no-store; no hay max-age, porque los modelos de lenguaje no tienen reloj
  • .invalidates(patterns) declara qué dominios una llamada exitosa pone stale, con patrones dot-glob (* un segmento, ** cualquier profundidad)
  • las pistas se compilan en políticas dentro de un PolicyEngine first-match-wins, con resolución memoizada

Qué viaja por el cable

Dos intercepciones a nivel de protocolo, no middleware:

En tools/list, las descripciones decoradas llevan la directiva: Retrieve an invoice by ID [Cache-Control: immutable]. El agente ve la regla de reutilización en el momento de planificar.

En tools/call, solo las llamadas exitosas disparan la invalidación (una mutation isError fallida no emite nada). La respuesta gana una etiqueta legible por máquina como su primer elemento de contenido, situada en el índice 0 para que la truncación no la recorte:

xml
<cache_invalidation cause="billing.refund" domains="billing.get_*" />

y cada URI stale dispara notifications/resources/updated con un mcpfusion://stale/{pattern} sintético, así los clientes con suscripciones saben exactamente qué descartar.

Recursos y suscripciones

Los recursos reales pasan por f.resource() (o defineResource): una plantilla de URI como invoices://{id}, un handler que devuelve { text } o { blob }, un flag subscribable y anotaciones de audiencia. Las URIs de plantilla se resuelven con matching estilo RFC-6570.

En MCP 2.0 el stream subscriptions/listen es de primera clase: un cliente registra un filtro (toolsListChanged, promptsListChanged, resourcesListChanged, URIs concretas de resourceSubscriptions), el servidor lo confirma con el filtro honrado y emite los eventos coincidentes. La entrega es best-effort y nunca bloquea el pipeline: una notificación se descarta, no se aplica backpressure.

Caché de listas (SEP-2549)

Separado del staleness de las tools, MCP 2.0 permite a un servidor adjuntar metadatos de caché a los resultados de listado: ttlMs y scope (private por cliente, public compartido) en tools/list, prompts/list y las listas de recursos, configurados con attachToServer({ listCacheTtlMs, listCacheScope }). Por defecto: 5 minutos, private. Un proxy delante de tu conector puede servir las llamadas de listado repetidas desde su caché, que es la respuesta del protocolo a los clientes charlatanes en cold start. Define listCacheTtlMs: 0 y los metadatos desaparecen con overhead cero.

El manifiesto dinámico y la introspección

attachToServer({ introspection: { enabled: true } }) registra el recurso mcpfusion://manifest.json: cada tool, acción, presenter, clave de schema, flag destructivo y flag de regla contextual, filtrado por RBAC en cada lectura con un contexto fresco de contextFactory, para que dos sesiones lean el manifiesto que les corresponde. La misma maquinaria de materializar y digerir es la que Governance blinda.

Por qué no max-age

Porque no hay ningún temporizador dentro del modelo: no puede comprobar una cabecera de frescura, solo se le puede avisar. immutable significa "reutiliza en esta conversación salvo que yo diga lo contrario"; los eventos de invalidación son ese "lo contrario". El vocabulario HTTP se toma prestado precisamente porque los modelos están entrenados con él, y el sufijo [Cache-Control] viaja dentro de la descripción de la tool, el único texto que el agente lee de forma fiable antes de planificar.

Próximos pasos