Skip to main content
Use fentaris() as the app boundary, declare upstream MCP servers with high-level helpers, and keep local endpoint defaults in fentaris.json.

Prerequisites

  • Node.js 20+ (LTS recommended)
  • pnpm or npm

Quick Start

pnpm add @fentaris/core
Create fentaris.json at the project root:
{
  "name": "fentaris-proxy",
  "packageManager": "pnpm",
  "entrypoint": "src/index.ts",
  "port": 4000,
  "path": "/mcp",
  "authDir": ".fentaris"
}
Then create src/index.ts:
import { fentaris, stdio } from "@fentaris/core";

const app = fentaris();

app.server("filesystem", {
  displayName: "Filesystem",
  transport: stdio({
    command: "npx",
    args: ["-y", "@modelcontextprotocol/server-filesystem", "./demo-files"],
  }),
});

await app.start();
app.start() reads port and path from the nearest fentaris.json when they are not passed explicitly. With the config above, the proxy listens at http://localhost:4000/mcp.

Add a Second Server

Register each upstream MCP server with its own app.server(...) call:
app.server("custom", {
  displayName: "Custom Tools",
  transport: stdio({
    command: "node",
    args: ["./dist/server.js"],
  }),
  env: (user) => ({
    USER_ID: user.id ?? "anonymous",
  }),
});
The first argument is the Fentaris server name. Fentaris uses it for routing, policy, middleware context, logs, hooks, and proxied tool names.
app.server(...) is an alias for app.mcp(...). Use app.server(...) when the page is explaining upstream server registration, and use policy APIs for access control.

Add Middleware

app.use(async (ctx, next) => {
  ctx.log.info("tool-call", {
    server: ctx.server?.name,
    tool: ctx.tool?.name,
  });

  return next();
});
Middleware runs before matching calls are forwarded to the upstream MCP server. Use it for request-time checks, logging, argument validation, and approval workflows.

Strict TypeScript Example

In strict TypeScript projects, annotate exported middleware and policies at the boundary and let callback parameters infer from Fentaris:
import {
  bearer,
  credential,
  credentialJson,
  fentaris,
  group,
  mcp,
  policy,
  streamableHttp,
  user,
  type Middleware,
} from "@fentaris/core";

const requestTags: Middleware = async (ctx, next) => {
  ctx.log.setTag("subject", ctx.subject?.id ?? "anonymous");
  ctx.log.setTag("operation", ctx.operation);

  if (ctx.server?.name === "github" && ctx.tool?.name === "delete_repo") {
    return ctx.deny("Repository deletion is not allowed from this proxy.");
  }

  return next();
};

const maintainers = policy("maintainers")
  .mcp("github")
  .allow("search_issues")
  .mcp("github")
  .allow("create_issue");

const app = fentaris({
  autoLog: true,
  servers: [
    mcp("github", {
      transport: streamableHttp({ url: "https://github.example/mcp" }),
      auth: bearer(credential("github.token")),
    }),
  ],
  groups: [
    group({
      id: "maintainers",
      users: [
        user("alice", {
          apiKeys: [credentialJson("users.alice.apiKeys.0")],
        }),
      ],
      credentials: {
        "github.token": credentialJson("groups.maintainers.github.token"),
      },
      policy: maintainers,
    }),
  ],
});

app.use(requestTags);

await app.start();
Use type Middleware for shared middleware functions. Inline app.use(async (ctx, next) => ...) callbacks also infer contextual ctx and next types. The declarative fentaris({ ... }) form keeps group credentials next to the users and policy that own them. Use incremental calls such as app.server(...), app.policy(...), and app.group(...) when the app does not need to declare group- or user-scoped credential sources.
app.group("maintainers").mcp("github") returns a scoped middleware and event handle for an already declared server. It does not register an upstream transport or group credentials.

Connect an MCP Client

Point your MCP client to the endpoint from fentaris.json:
http://localhost:4000/mcp
Fentaris exposes one endpoint and forwards allowed requests to the registered upstream MCP servers.
Change port and path in fentaris.json instead of hardcoding endpoint settings in src/index.ts.

Verify Available Tools

If you are working in Node, you can list tools directly:
const tools = await app.listTools(undefined, { id: "demo-user" });
console.log(tools.tools.map((tool) => tool.name));

Low-Level API

The class constructors are still available for advanced cases that need explicit object construction:
import { McpProxy, McpServer, StdioTransport } from "@fentaris/core";
For normal applications, prefer fentaris(), app.server(...), and stdio(...).