Skip to main content
These are all control-flow concerns, which means middleware is the right primitive.

Rate limiting

app.use(
  rateLimit({
    max: 60,
    window: "1m",
  })
);
By default, the built-in rate limiter keys by:
  • IP
  • forwarded IP
  • session id
  • request id fallback

Caching

app.use(
  cache({
    ttl: 30_000,
  })
);
The built-in cache stores successful results in memory by tool name and parsed input.

Sharing state through ctx

Middleware and hooks can attach metadata to ctx for later use in the handler:
app.use(
  middleware(async ({ request, ctx, next }) => {
    ctx.tenantId = request.headers["x-tenant-id"] ?? "public";
    return next();
  })
);
Then the tool reads it:
handler: ({ ctx }) => {
  return { tenantId: ctx.tenantId };
};

Rule of thumb

  • request is read-only metadata from the transport
  • ctx is mutable request-scoped state you want to reuse later