MCP Fusion/Protocol and runtime/Formato do wire
Formato do wire
Os bytes exatos que um Presenter envia: os blocos de texto ordenados, o XML de ui_passthrough, as regras de domínio, as sugestões de ação, a guilhotina _select, os embeds e o TOON.
Uma chamada de tool com um Presenter não retorna JSON.stringify. Ela retorna uma sequência ordenada de blocos de texto, cada um com um formato estável que o agente (e qualquer UI do cliente) consegue interpretar. Esta página é a especificação desse formato.
A sequência de blocos
ResponseBuilder.build() emite até seis blocos, sempre nesta ordem:
| # | Bloco | Formato |
|---|---|---|
| 1 | data | o payload mascarado e serializado, como texto JSON |
| 2 | UI blocks | um <ui_passthrough> por bloco |
| 3 | embeds | blocos brutos de Presenters filhos |
| 4 | directives | lista de <llm_directives> |
| 5 | rules | lista de <domain_rules> |
| 6 | suggestions | lista de <action_suggestions> |
Um bloco de UI fica assim no wire:
<ui_passthrough type="echarts" title="Revenue by month" width="full" priority="1">
echarts-fenced-content: the chart config as formatted JSON
</ui_passthrough>Os atributos type, title, width (full, half, third) e priority dizem a um renderer de cliente o que é o conteúdo cercado. ui.table e ui.list são markdown; ui.json é json cercado; ui.codeBlock mantém sua tag de linguagem. Não existe um tipo de bloco de tabela nativo: o cercado é o contrato, e fence() é o único lugar que muda se o MCP padronizar os blocos de UI.
Os blocos de regras e sugestões são listas de bullets simples:
<domain_rules>
- amount_cents is in CENTS. Divide by 100.
</domain_rules>
<action_suggestions>
- billing.remind: Send a payment reminder
</action_suggestions>structuredContent é um sistema à parte
O campo structuredContent do MCP 2.0 não é produzido por Presenters. Para emiti-lo, use successStructured(data), que envia o bloco de texto JSON para clientes MCP 1.0 mais o campo legível por máquina structuredContent, e declare o formato no wire com .withOutputSchema(schema). Os Presenters cuidam do texto para humanos e LLMs; successStructured cuida do canal de objeto parseado. Não confunda os dois.
_select: a guilhotina de campos
.enableSelect() adiciona um parâmetro de array _select a toda ação da tool, cujo enum é a união das chaves raiz do schema de todas as ações. Quando o agente passa _select, o bloco data é filtrado para essas chaves. Todo o resto (blocos de UI, regras, sugestões) continua rodando sobre o objeto completo, então um cliente que renderiza a resposta para um humano vê o panorama inteiro enquanto o contexto do modelo carrega só o que ele pediu. Essa é a economia de tokens mais barata em modelos largos, e é opt-in por builder.
embed: composição relacional
.embed('lines', InvoiceLinesPresenter) lê data.lines por meio de um Presenter filho e mescla os blocos e as regras do filho na resposta do pai. O pai possui uma entidade; um grafo de entidades é uma árvore de Presenters, cada um com seu próprio schema, mascaramento e limites.
TOON: descrições com metade dos tokens
.toonDescription() e toonSuccess(data) usam o formato TOON delimitado por pipes para coleções uniformes: chaves repetidas colapsam em uma linha de cabeçalho e o array vira uma tabela de valores. Para uma lista de dez mil itens com o mesmo formato, a contagem de tokens cai para cerca da metade. O formato é gerado a partir do schema da ação, então o agente vê action|desc|required como camada um e os dados como camada dois.
A porta lateral dos testes
Toda resposta de um Presenter carrega um symbol não-enumerável com { data, systemRules, uiBlocks }. @mcpfusion/testing lê a visão estruturada por meio dele em vez de reanalisar o XML, e é assim que result.data, result.systemRules e result.uiBlocks em Testing são exatos. JSON.stringify nunca vê o symbol.
Regras implícitas de empacotamento
O valor de retorno de um handler é interpretado com uma única regra: objetos ToolResponse com a marca passam intocados, todo o resto é embrulhado como success(data). Um retorno null ou undefined vira o texto OK. É por isso que você nunca monta à mão formatos { content: [...] }, e é por isso que adicionar um Presenter nunca exige mexer no handler.
Próximos passos
- Errors: o envelope
<tool_error>, a outra metade do formato - Models and Presenters: construindo os formatos
- Testing: asserções sobre os blocos
