Skip to main content
Middleware runs for tool calls and governed resource, prompt, and completion operations. Use it for access control, logging, request shaping, and consistent policy enforcement. New middleware receives one unified ctx object plus next. If you need finer-grained hooks (only on specific tools), see Hooks.

When to use middleware

Use middleware when you need a decision or transformation to apply to every request:
  • Authentication and authorization checks.
  • Input normalization or validation.
  • Logging, metrics, and tracing tags.
  • Guardrails for destructive or high-cost tools.
If the logic is only for a single tool, operation, or server, prefer proxy.tool(...), proxy.operation(...), or proxy.mcp(name).operation(...) routes to avoid slowing the full pipeline.

Basic middleware

Why it matters: logging in middleware ensures every tool call is observed, regardless of which server it hits.

Deny a tool

Tool patterns use public server.tool names such as github.create_issue, github.*, and *.search_*.

Server-scoped middleware

Tip: use a centralized allow/deny list to avoid scattering policy rules across multiple middlewares.

Check policy capabilities

ctx.policy.can(server, tool) checks the configured allow/deny permissions for the current subject. It does not consume rate limits, invoke manual approval callbacks, or expose credential values.

Inject guidance

This is useful when you want the model to be aware of environment-specific constraints (for example: “Only read from /tmp”).

Enforce plan-based policies

Validate inputs before they reach servers

You can block requests early by checking parameters. This is useful when a server does not validate inputs on its own.

Normalize inputs

Middleware can also clean up incoming parameters before they reach a server.

Add soft guidance for LLM clients

If your MCP clients are LLMs, you can inject guidance without blocking requests. This is useful for reminders like “do not write outside /tmp”.

Pattern: lightweight rate limiting

Fentaris does not implement rate limiting directly, but you can implement a simple per-user limit inside middleware. For production, use a shared store (Redis, KV) to coordinate across multiple instances.

Migrating legacy middleware

ctx.user, ctx.policyDecision, and ctx.res remain available as compatibility aliases. Prefer ctx.subject, ctx.auth, ctx.policy, ctx.credentials, ctx.response, and the root helpers in new code.

Debugging middleware

If requests are not behaving as expected, log ctx.operation, ctx.server?.name, ctx.tool?.proxyName, ctx.resource?.uri, ctx.prompt?.name, and ctx.completion?.target. This will quickly tell you whether a route is matching the capability you expect.

Local capabilities

Local capabilities declared with app.local(name) use the same middleware and operation route pipeline as upstream MCP servers.
For local tool handlers, ctx.server?.name is the local namespace and ctx.tool?.name is the unproxied local tool name. Local resources, prompts, and completions expose the same context fields as upstream operations.

Ordering rules

Middleware runs in registration order. If a middleware returns a CallToolResult, Fentaris stops the chain.

Best practices

  • Keep side effects minimal; prefer deterministic checks.
  • Log denied calls with the reason to ease audits.
  • Avoid long-running operations; do those in the upstream server when possible.
  • Use one middleware per concern (logging, policy, normalization).
  • Write tests for policy middleware to avoid regressions.