MCP Fusion/Protocol and runtime/Arquitetura de runtime

Arquitetura de runtime

Pergunte à IA sobre a Vinkius

Como o MCP Fusion realmente executa: compilação em build time, roteamento de requisições O(1), contexto por requisição, o mónade Result, o pipeline de execução e os transports.

Esta página descreve o motor por baixo da API fluida documentada em Tools. É a referência técnica aprofundada do ciclo de vida de requisição, compilada em packages/core.

Duas camadas: compile uma vez, sirva muitas

O MCP Fusion separa o trabalho que pertence ao build time do trabalho que precisa acontecer em toda requisição.

Build time. registry.register(builder) chama builder.buildToolDefinition() imediatamente. Essa passagem de compilação produz tudo de que o servidor precisa pela vida do processo:

  • o schema de entrada mesclado, com o campo discriminador e as anotações obrigatórias por ação
  • os schemas de validação prontos para o <tool_error>, um schema Zod strict por ação
  • a cadeia de middleware pré-compilada, um closure por ação, middleware global por fora e middleware por ação por dentro
  • o mapa de ações, um Map O(1) da chave de ação ao contexto compilado

Depois de buildToolDefinition() o builder fica congelado: Object.freeze nas ações, e uma guarda em todo método mutante. Você não consegue alterar uma tool construída por acidente. mergeActions() é a única forma autorizada de adicionar ações, e ela descongela explicitamente para reconstruir.

Request time. O caminho de runtime é fixo e não tem etapa de montagem:

contextFactory → discriminator parse → action resolve (Map)
  → arg validation (cached strict schema) → middleware chain (precompiled)
  → handler → Presenter (postProcess) → guards → response

Toda etapa lê de uma estrutura pré-compilada. Não há schema reconstruído, cadeia composta nem busca varrida no caminho da requisição.

Registry, nomes de tools e ações

Um nome de builder com ponto não é um nome plano de tool. f.query('billing.get_invoice') registra a ação get_invoice na tool billing. Dois nomes com um ponto no build time são erro; o aninhamento usa f.router('support.tickets'). Builders que compartilham um namespace são mesclados: três arquivos exportando compliance.scan, compliance.report e compliance.status viram uma única tool compliance com três ações, então você pode organizar um conector por diretório e o agente ainda vê uma superfície coerente.

contextFactory: a entrada por requisição

attachToServer(server, { contextFactory }) executa contextFactory(extra) uma vez por requisição, antes do roteamento. extra é o contexto de requisição bruto do SDK do MCP: o id de sessão, o objeto _meta (token de progresso), o AbortSignal para cancelamento e o canal sendRequest para elicitação. Retorne um objeto novo; o framework o altera no lugar conforme o middleware o enriquece. É aqui que handles de banco de dados, ids de tenant e o sinal de abort entram em ctx, então uma implantação stateless não tem vazamento entre requisições por construção.

Se você omitir contextFactory, acessar ctx lança um erro explícito em vez de falhar misteriosamente.

Os transports

startServer() suporta três transports:

TransportSessãoUso
stdionenhumaclientes locais, os apps de desktop, mcpfusion dev
httpid de sessão UUID, ceifador de TTL, limite de corpo, token bucket por sessãoum servidor MCP HTTP persistente
statelessnenhumamodo MCP 2.0: um Server novo por requisição, atrás de qualquer load balancer

Stateless é o padrão de escala: sem handshake initialize, as requisições são roteadas pelos headers Mcp-Method e Mcp-Name, e os adaptadores Vercel e Cloudflare servem respostas JSON sem SSE e sem estado de sessão. O Vinkius Edge usa esse modelo: o seu StartServerOptions.state é serializado antes de o isolate ser descartado e restaurado de forma transparente na próxima requisição fria.

Mónade Result: control flow sem exceções

Internamente o pipeline é baseado em Result: Success ou Failure, cada etapa encerra em curto sobre Failure. Os handlers não veem Result; eles devolvem dados ou succeed()/toolError(). O que Result compra é previsibilidade: validação, análise do discriminador e dispatch devolvem valores, não exceções lançadas, então nada se perde em uma stack trace. Um ToolResponse lançado passa intacto (então throw toolError('NOT_FOUND', ...) funciona); qualquer outro throw é envolvido como INTERNAL_ERROR com uma recuperação do tipo "não repita às cegas".

Guardas em torno do handler

Três guardas independentes envolvem a execução e têm custo zero quando não configuradas:

  • Concorrência: um semáforo com fila limitada. Acima da capacidade devolve SERVER_BUSY com uma dica de retry em vez de degradar.
  • Serialização de mutações: uma ação destrutiva recebe automaticamente um mutex FIFO por ação, então dois agentes não disputam a mesma escrita.
  • Limite de egresso: um orçamento de bytes na resposta. Texto grande demais é truncado com uma mensagem de sistema dizendo ao modelo para paginar. O structuredContent estruturado é preservado; isError nunca é invertido.

Veja Security pipeline para as guardas do lado da entrada e State sync para sinais de cache e obsolescência.

Hooks de observabilidade

registry.enableDebug(observer), enableTracing(tracer) e enableTelemetry(sink) se propagam a todo builder pela mesma interface duck-typed. Cada hook é inerte até ser definido: sem timer, sem wrapper, sem alocação quando um subsistema está silencioso. Os mesmos três sinks alimentam o mcpfusion inspect, o pipeline OTel e a evidência de cobertura de middleware do lockfile.

Próximos passos