MCP Fusion/Core concepts/Middleware y contexto
Middleware y contexto
Combina autenticación, aislamiento de tenants y limitación de tasa con derivación de contexto tipada: una llamada a defineMiddleware enriquece ctx para cada handler posterior, compilada en O(1) durante el build.
Middleware es donde un conector deja de ser un conjunto de funciones y se convierte en un servicio: se resuelve la identidad, se aíslan los tenants, se limita el tráfico y se audita cada llamada. El modelo de middleware de MCP Fusion es pequeño, está tipado de extremo a extremo y se compila antes de la primera solicitud.
Dos formas, un concepto
Derivación de contexto es la forma idiomática. Un middleware devuelve las partes que añade 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 de la tool la conecta:
f.query('billing.list_invoices')
.use(withUser)
.handle(async (input, ctx) => {
return ctx.db.invoices.findMany({
where: { userId: ctx.user.id },
});
});El sistema de tipos transporta la derivación: .use(withUser) cambia el tipo de contexto del builder a AppContext & { user }, así que ctx.user compila dentro del handler y quitar el middleware rompe el build. No hay ningún cast en tiempo de ejecución que descubrir en producción.
También existe la forma clásica
El middleware que necesita actuar antes y después, o interrumpir el flujo, 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;
};Devolver un ToolResponse desde un middleware interrumpe el flujo: nunca se llama a next() y esa respuesta es la respuesta final. Así inputFirewall y rateLimit rechazan una llamada sin tocar el handler. Olvidar return next() activa una advertencia única en la consola, porque todo desarrollador comete ese error alguna vez.
Lanzar una excepción también funciona: throw toolError('NOT_FOUND', ...) atraviesa el pipeline con el código y la recuperación intactos. Cualquier otro valor lanzado se envuelve como INTERNAL_ERROR.
Orden y ámbito
Tres ámbitos se componen en una cadena por acción:
| Ámbito | Declarado en | Posición |
|---|---|---|
| Global | f.middleware() y a nivel del registry | más externo |
| Grupo | ActionGroupBuilder.use() | intermedio |
| Tool o acción | .use() en el builder | más interno |
El middleware global se ejecuta primero, con la autenticación antes que todo, y el middleware por acción se ejecuta al final, más cerca de tu handler. Las cadenas se compilan en buildToolDefinition() en un closure por acción, así que la ruta de la solicitud es una llamada, no un bucle sobre un array.
Los componentes integrados
El framework incluye tres middlewares que son middlewares normales, no magia:
inputFirewall({ judge }): LLM-as-Judge sobre los argumentos, falla cerrada y devuelveINPUT_REJECTEDrateLimit({ windowMs, limit, keyFn }): ventana deslizante sobre timestamps; las solicitudes rechazadas no se registran, así que un cliente abusivo no puede prolongar su propio bloqueoauditTrail({ sink, hashArgs }): emite un evento por llamada con un digest SHA-256 de los argumentos, nunca los argumentos mismos
El middleware de autenticación procede de los paquetes dedicados: requireJwt, requireApiKey y requireAuth. Consulta Autenticación.
Aislamiento por construcción
contextFactory se ejecuta por solicitud y devuelve un objeto nuevo; el middleware escribe después las claves derivadas en él con una protección que omite __proto__, constructor y prototype. Por tanto, dos solicitudes simultáneas no pueden compartir un id de tenant, un usuario ni un handle de base de datos, aunque lleguen al mismo proceso de larga duración. En un transporte stateless cada solicitud obtiene un servidor nuevo; en stdio, la misma garantía procede del objeto de contexto por solicitud.
Este es el mecanismo detrás de Conectores multi-tenant: el aislamiento es una propiedad del runtime, no una disciplina que tus handlers deban recordar.
Siguientes pasos
- Autenticación: JWT, claves de API y el flujo de dispositivo OAuth
- Conectores multi-tenant: un conector, muchos tenants
- Arquitectura del runtime: dónde encaja el middleware en el pipeline
