Skip to main content
Combine routing, tenant identity, policy, and logging in one proxy
These examples use the high-level Fentaris API. Start here when you want production-shaped wiring without dropping into low-level constructors.

Multi-Tenant Tool Routing

Use UserContext to route requests and inject tenant-specific environment variables.
import { fentaris, mcp, stdio } from "@fentaris/core";

const proxy = fentaris({
  servers: [
    mcp("analytics", {
      displayName: "Analytics",
      transport: stdio({
        command: "node",
        args: ["./dist/analytics-server.js"],
      }),
      env: (user) => ({
        TENANT_ID: String(user.tenantId ?? "unknown"),
      }),
    }),
  ],
  user: (req) => ({
    id: req.headers["x-user-id"],
    tenantId: req.headers["x-tenant-id"],
  }),
});

await proxy.start();

Why it matters

  • Each tenant sees only their own data.
  • You can keep the MCP server unchanged.

Operational Tips

  • Use a consistent tenantId key across middleware, logs, and env injection.
  • Validate headers at the edge; never trust user input directly.

Policy Enforcement with Middleware

proxy.use(async (ctx, next) => {
  const isAdmin = ctx.user.role === "admin";
  if (ctx.tool?.proxyName === "filesystem__delete" && !isAdmin) {
    return ctx.deny("Only admins can delete files.");
  }

  return next();
});

Why it matters

  • Centralized access control.
  • A single place to audit policy changes.

Operational Tips

  • Log denies with the reason and user.id.
  • Keep policy text human-readable; it becomes part of your audit trail.

Observability with Trace Tags

proxy.use(async (ctx, next) => {
  ctx.log.setTag("trace", ctx.user.traceId ?? "no-trace");
  ctx.log.info("tool-call", { tool: ctx.tool?.proxyName });
  return next();
});

Why it matters

  • Correlate tool calls across services.
  • Keep logs structured and consistent.

Operational Tips

  • Make sure traceId is propagated from your API gateway.
  • Avoid logging payloads that may contain PII.

Server-Scoped Hook for Diagnostics

proxy.mcp("diagnostics").on("tool:success", async ({ ctx, durationMs }) => {
  ctx.log.info("diagnostics.tool.success", {
    tool: ctx.tool?.name,
    durationMs,
  });
});

Why it matters

  • Hooks let you target a subset of tool calls.
  • Perfect for audits and analytics.

Operational Tips

  • Send diagnostic logs to a lower-retention index.
  • Use server-scoped handles such as proxy.mcp("diagnostics") to avoid ambiguity.

Putting It All Together

In a real deployment, you will combine these patterns:
  • User resolver for tenant identity and trace info.
  • Middleware for policy and environment-specific rules.
  • Hooks for targeted audits and analytics.
When in doubt, start with the Tutorial and expand from there.

Common Pitfalls

  • Forgetting to close the proxy leads to orphaned server processes.
  • Using unstable server names breaks tool prefixes for clients.
  • Logging sensitive payloads can create compliance issues.

Low-Level API

Use McpProxy, McpServer, and explicit transport classes only when you need direct construction or compatibility with older examples.