MCP Fusion/Protocol and runtime/Arquitetura de runtime
Arquitetura de runtime
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 Zodstrictpor 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
MapO(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 → responseToda 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:
| Transport | Sessão | Uso |
|---|---|---|
stdio | nenhuma | clientes locais, os apps de desktop, mcpfusion dev |
http | id de sessão UUID, ceifador de TTL, limite de corpo, token bucket por sessão | um servidor MCP HTTP persistente |
stateless | nenhuma | modo 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_BUSYcom 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
structuredContentestruturado é preservado;isErrornunca é 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
- Security pipeline: o firewall e suas camadas
- State sync: diretivas de cache, invalidação, assinaturas
- Testing: exercite este pipeline em memória
