> ## Documentation Index
> Fetch the complete documentation index at: https://fentaris.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Middleware

> Enforce policy and transform tool calls.

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](/guides/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

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  ctx.log.info("tool-call", {
    subject: ctx.subject?.id ?? "anonymous",
    authenticated: ctx.auth.authenticated,
    allowed: ctx.policy.allowed,
  });

  return next();
});
```

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

## Deny a tool

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.tool("filesystem.delete", async (ctx) => {
  return ctx.deny("delete is disabled for this environment");
});
```

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

## Server-scoped middleware

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const github = proxy.mcp("github");

github.use(async (ctx, next) => {
  if (ctx.subject?.hasGroup("support") === false) {
    return ctx.deny("GitHub tools require support group membership.");
  }

  return next();
});
```

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

## Check policy capabilities

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  if (ctx.subject?.hasGroup("admins") && ctx.policy.can("github", "delete_repo")) {
    ctx.log.info("admin-delete-capability", {
      subject: ctx.subject.id,
      groups: ctx.subject.groups.map((group) => group.id),
    });
  }

  return next();
});
```

`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

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  ctx.inject("Remember to avoid destructive actions.");
  return next();
});
```

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

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  if (ctx.subject?.metadata?.plan !== "enterprise" && ctx.tool?.proxyName === "analytics__export") {
    return ctx.deny("Export is available only on the enterprise plan.");
  }
  return next();
});
```

## 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.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.tool("*.search_*", async (ctx, next) => {
  if (typeof ctx.args?.query === "string") {
    if (ctx.args.query.length > 200) {
      return ctx.deny("Query too long.");
    }
  }
  return next();
});
```

## Normalize inputs

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

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.tool("*.search_*", async (ctx, next) => {
  if (typeof ctx.args?.query === "string") {
    ctx.args.query = ctx.args.query.trim();
  }
  return next();
});
```

## 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”.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  ctx.inject("Use read-only tools unless explicitly asked to write.");
  return next();
});
```

## 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.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const calls: Record<string, number> = {};

proxy.use(async (ctx, next) => {
  const userId = ctx.subject?.id ?? ctx.auth.userId ?? "anonymous";
  calls[userId] = (calls[userId] ?? 0) + 1;
  if (calls[userId] > 1000) {
    return ctx.deny("Rate limit exceeded.");
  }
  return next();
});
```

## Migrating legacy middleware

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (request, context, next) => {
  context.log.info("legacy", { tool: request.proxyToolName });
  return next();
});

proxy.use(async (ctx, next) => {
  ctx.log.info("unified");
  return next();
});
```

`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.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
app.local("workspace")
  .tool("status", { inputSchema: { type: "object" } }, async (ctx) => ({
    content: [{ type: "text", text: ctx.server?.name ?? "workspace" }],
  }));

app.mcp("workspace").use(async (ctx, next) => {
  ctx.log.info("workspace.local", {
    operation: ctx.operation,
    tool: ctx.tool?.name,
    resource: ctx.resource?.uri,
    prompt: ctx.prompt?.name,
  });

  return next();
});
```

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.

## Reference links

* MCP SDK: [https://github.com/modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/sdk)
