MCP Fusion/Core concepts/Middleware e contexto
Middleware e contexto
Componha autenticação, isolamento de locatários e limitação de taxa com derivação de contexto tipada: uma chamada a defineMiddleware enriquece ctx para todos os handlers seguintes, compilada em O(1) no build.
Middleware é onde um conector deixa de ser um conjunto de funções e se torna um serviço: a identidade é resolvida, os locatários são isolados, o tráfego é limitado e cada chamada é auditada. O modelo de middleware do MCP Fusion é pequeno, tipado de ponta a ponta e compilado antes da primeira requisição.
Duas formas, um conceito
Derivação de contexto é a forma idiomática. Um middleware retorna as partes que adiciona a ctx:
import { initMCPFusion } from '@mcpfusion/core';
interface AppContext {
db: PrismaClient;
user?: { id: string; role: 'viewer' | 'admin' };
}
const f = initMCPFusion<AppContext>();
const withUser = f.middleware(async (ctx) => {
const token = ctx.headers?.authorization;
const user = await verify(token);
if (!user) throw f.error('UNAUTHORIZED', 'Sign in first');
return { user };
});Uso da tool a conecta:
f.query('billing.list_invoices')
.use(withUser)
.handle(async (input, ctx) => {
return ctx.db.invoices.findMany({
where: { userId: ctx.user.id },
});
});O sistema de tipos transporta a derivação: .use(withUser) muda o tipo de contexto do builder para AppContext & { user }, então ctx.user compila dentro do handler e remover o middleware quebra o build. Não há um cast em tempo de execução para descobrir em produção.
A forma clássica também existe
O middleware que precisa agir antes e depois, ou interromper o fluxo, usa (ctx, args, next):
import type { MiddlewareFn } from '@mcpfusion/core';
const audit: MiddlewareFn<AppContext> = async (ctx, args, next) => {
const started = Date.now();
const result = await next();
ctx.audit?.({ tool, args, ms: Date.now() - started });
return result;
};Retornar um ToolResponse a partir de um middleware interrompe o fluxo: next() nunca é chamado e essa resposta é a resposta. É assim que inputFirewall e rateLimit rejeitam uma chamada sem tocar no handler. Esquecer return next() gera um aviso único no console, porque todo desenvolvedor comete esse erro uma vez.
Lançar uma exceção também funciona: throw toolError('NOT_FOUND', ...) passa pelo pipeline com o código e a recuperação intactos. Qualquer outro valor lançado é envolvido como INTERNAL_ERROR.
Ordem e escopo
Três escopos se combinam em uma cadeia por ação:
| Escopo | Declarado em | Posição |
|---|---|---|
| Global | f.middleware() e no nível do registry | mais externo |
| Grupo | ActionGroupBuilder.use() | intermediário |
| Tool ou ação | .use() no builder | mais interno |
O middleware global executa primeiro, com a autenticação antes de tudo, e o middleware por ação executa por último, mais próximo do seu handler. As cadeias são compiladas em buildToolDefinition() em um closure por ação, então o caminho da requisição é uma chamada, não um loop sobre um array.
Os componentes integrados
Três middlewares vêm com o framework e são middlewares comuns, não mágica:
inputFirewall({ judge }): LLM-as-Judge nos argumentos, falha fechada e retornaINPUT_REJECTEDrateLimit({ windowMs, limit, keyFn }): janela deslizante sobre timestamps; requisições rejeitadas não são registradas, então um cliente abusivo não pode estender o próprio bloqueioauditTrail({ sink, hashArgs }): emite um evento por chamada com um digest SHA-256 dos argumentos, nunca os próprios argumentos
O middleware de autenticação vem dos pacotes dedicados: requireJwt, requireApiKey e requireAuth. Consulte Autenticação.
Isolamento por construção
contextFactory executa por requisição e retorna um objeto novo; o middleware então grava as chaves derivadas nele com uma proteção que ignora __proto__, constructor e prototype. Duas requisições concorrentes não podem compartilhar um id de locatário, um usuário ou um handle de banco de dados, mesmo quando atingem o mesmo processo de longa duração. Em um transporte stateless, cada requisição recebe um servidor novo; em stdio, a mesma garantia vem do objeto de contexto por requisição.
Esse é o mecanismo por trás de Conectores multi-tenant: o isolamento é uma propriedade do runtime, não uma disciplina que seus handlers precisam lembrar.
Próximos passos
- Autenticação: JWT, chaves de API e o fluxo de dispositivo OAuth
- Conectores multi-tenant: um conector, muitos locatários
- Arquitetura do runtime: onde o middleware fica no pipeline
