MCP Fusion/Protocol and runtime/Formato del wire

Formato del wire

Pregunta a la IA sobre Vinkius

Los bytes exactos que un Presenter envía: los bloques de texto ordenados, el XML de ui_passthrough, las reglas de dominio, las sugerencias de acción, la guillotina _select, los embeds y TOON.

Una tool call con un Presenter no devuelve JSON.stringify. Devuelve una secuencia ordenada de bloques de texto, cada uno con una forma estable que el agente (y cualquier UI del cliente) puede parsear. Esta página es la especificación de ese formato.

La secuencia de bloques

ResponseBuilder.build() emite hasta seis bloques, siempre en este orden:

#BloqueForma
1datael payload enmascarado y serializado, como texto JSON
2UI blocksun <ui_passthrough> por bloque
3embedsbloques crudos de Presenters hijos
4directiveslista de <llm_directives>
5ruleslista de <domain_rules>
6suggestionslista de <action_suggestions>

Un bloque de UI se ve así en el wire:

xml
<ui_passthrough type="echarts" title="Revenue by month" width="full" priority="1">
  echarts-fenced-content: the chart config as formatted JSON
</ui_passthrough>

Los atributos type, title, width (full, half, third) y priority le dicen a un renderer del cliente qué es el contenido entre la cerca. ui.table y ui.list son markdown; ui.json es json entre cerca; ui.codeBlock conserva su language tag. No hay un tipo de bloque de tabla nativo: la cerca es el contrato, y fence() es el único lugar que cambia si MCP estandariza los bloques de UI.

Los bloques de reglas y sugerencias son simples listas de viñetas:

xml
<domain_rules>
- amount_cents is in CENTS. Divide by 100.
</domain_rules>
<action_suggestions>
- billing.remind: Send a payment reminder
</action_suggestions>

structuredContent es un sistema aparte

El campo structuredContent de MCP 2.0 no lo producen los Presenters. Para emitirlo usa successStructured(data), que envía el bloque de texto JSON para los clientes de MCP 1.0 más el campo legible por máquina structuredContent, y declara la forma en el wire con .withOutputSchema(schema). Los Presenters se ocupan del texto para humanos y LLMs; successStructured se ocupa del canal de objeto parseado. No confundas los dos.

_select: la guillotina de campos

.enableSelect() añade un parámetro de array _select a cada action de la tool, cuyo enum es la unión de las claves raíz del schema entre todas las actions. Cuando el agente pasa _select, el bloque data se filtra a esas claves. Todo lo demás (bloques de UI, reglas, sugerencias) sigue corriendo sobre el objeto completo, así que un cliente que renderiza la respuesta para un humano ve el panorama completo mientras el contexto del modelo lleva solo lo que pidió. Esta es la victoria de tokens más barata en modelos anchos, y es opt-in por builder.

embed: composición relacional

.embed('lines', InvoiceLinesPresenter) lee data.lines a través de un Presenter hijo y fusiona los bloques y las reglas del hijo en la respuesta del padre. El padre posee una entidad; un grafo de entidades es un árbol de Presenters, cada uno con su propio schema, su redacción y sus límites.

TOON: descripciones con la mitad de tokens

.toonDescription() y toonSuccess(data) usan el formato TOON delimitado por pipes para colecciones uniformes: las claves repetidas se colapsan en una fila de encabezado y el array se convierte en una tabla de valores. Para una lista de diez mil items con forma idéntica, la cantidad de tokens es aproximadamente la mitad. El formato se genera a partir del schema de la action, así que el agente ve action|desc|required como capa uno y los datos como capa dos.

La puerta lateral de las pruebas

Toda respuesta de un Presenter lleva un symbol no enumerable con { data, systemRules, uiBlocks }. @mcpfusion/testing lee la vista estructurada a través de él en lugar de volver a parsear el XML, y así es como result.data, result.systemRules y result.uiBlocks en Testing son exactos. JSON.stringify nunca ve el symbol.

Reglas implícitas de envoltorio

El valor devuelto por un handler se interpreta con una regla: los objetos ToolResponse con la marca pasan intactos, todo lo demás se envuelve como success(data). Un retorno null o undefined se convierte en el texto OK. Por eso nunca construyes a mano formas { content: [...] }, y por eso añadir un Presenter nunca exige tocar el handler.

Próximos pasos