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

# Proxy setup

> Configure a production-shaped Fentaris proxy with high-level declarations.

Use `fentaris(...)` as the application boundary. Put upstream servers, identity, policy, logging, endpoint settings, and runtime hooks in one config-driven proxy.

## Quick Start

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, group, mcp, policy, stdio, user } from "@fentaris/core";

const supportPolicy = policy("support")
  .mcp("github")
  .allow("create_issue")
  .mcp("docs")
  .allow("search");

const proxy = fentaris({
  servers: [
    mcp("github", {
      displayName: "GitHub",
      transport: stdio({ command: "github-mcp-server" }),
    }),
    mcp("docs", {
      displayName: "Docs",
      transport: stdio({ command: "node", args: ["./dist/docs-server.js"] }),
    }),
  ],
  groups: [
    group({
      id: "support",
      users: [user("alice")],
      policy: supportPolicy,
    }),
  ],
  port: Number(process.env.PORT ?? 3000),
  host: process.env.FENTARIS_HOST ?? "127.0.0.1",
  path: "/mcp",
  name: "fentaris-proxy",
  version: "0.1.0",
  autoLog: true,
});

await proxy.start();
```

## Choose a Stable Endpoint

Client integrations usually store the MCP endpoint. Keep `path` stable even if ports or hosts change.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  servers,
  port: Number(process.env.PORT ?? 3000),
  host: "127.0.0.1",
  path: "/mcp",
});
```

If you deploy behind a reverse proxy, keep the public URL stable and move only the internal service address. The default listener binds to `127.0.0.1`; use `host: "0.0.0.0"` only when the service is intentionally exposed by the deployment boundary.

## Configure Proxy Identity

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  servers,
  name: "fentaris-proxy",
  version: "0.1.0",
  port: 3000,
  host: "0.0.0.0",
  path: "/mcp",
});
```

`name` and `version` are surfaced to MCP clients during initialization. Treat them as control-plane identity for logs and client diagnostics.

## Add Stable Server Metadata

Server names become tool prefixes, so they should remain stable across environments.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
mcp("analytics", {
  displayName: "Analytics",
  transport: stdio({ command: "node", args: ["./dist/analytics-server.js"] }),
})
```

<Tip>
  Use the same server names in staging and production. Names such as `analytics-staging` change tool prefixes and can break clients.
</Tip>

## Add Request Identity Early

Resolve user, tenant, and trace metadata at the proxy edge.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  servers,
  user: (req) => ({
    id: req.headers["x-user-id"] as string | undefined,
    tenantId: req.headers["x-tenant-id"],
    traceId: req.headers["x-trace-id"],
    environment: req.headers["x-env"],
  }),
});
```

This makes audit, logging, policy, and upstream env injection use the same request context.

## Enforce Policy at the Edge

Use `policy(...)`, `group(...)`, and `user(...)` for durable authorization.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const adminPolicy = policy("admin-full-access").mcp("*").allow("*");

const proxy = fentaris({
  servers,
  groups: [
    group({
      id: "admins",
      users: [user("admin")],
      policy: adminPolicy,
    }),
  ],
});
```

Use middleware for request-time validation that depends on arguments, environment, or runtime state.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.mcp("github").tool("create_issue", async (ctx, next) => {
  if (typeof ctx.args?.title !== "string") {
    return ctx.deny("Issue title is required.");
  }

  return next();
});
```

## Register Servers After Construction

You can declare policies in `fentaris(...)` and attach upstream MCP servers with `app.mcp(...)` before `start()`. Fentaris validates the final server visibility when the proxy starts.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, group, policy, streamableHttp, user } from "@fentaris/core";

const app = fentaris({
  groups: [
    group({
      id: "limited",
      users: [user("guest")],
      policy: policy("limited").mcp("specification").allow("*"),
    }),
  ],
});

app.mcp("specification", {
  transport: streamableHttp({
    url: "https://mcp.specification.website/mcp",
  }),
});
```

<Note>
  If a policy references a server that is still missing at `app.start()`, Fentaris fails startup with `FENTARIS_CONFIG_POLICY_SERVER_NOT_VISIBLE`.
</Note>

## Compose Governance After Construction

Use `app.policy(...)`, `app.group(...)`, and `app.mcp(...)` together when modules need to contribute to one proxy instance.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, stdio, user } from "@fentaris/core";

const app = fentaris({
  port: Number(process.env.PORT ?? 3000),
  path: "/mcp",
});

app.policy("readonly")
  .mcp("github")
  .allow("search_issues");

app.group("guests")
  .users(user("guest"))
  .policy("readonly");

app.mcp("github", {
  displayName: "GitHub",
  transport: stdio({ command: "github-mcp-server" }),
});

await app.start();
```

Repeated `app.policy("readonly")` calls return the same named policy, so modules can add permissions to a shared declaration before the proxy starts.

## Harden Tool Discovery

Filter tool lists before exposing them to clients.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.on("tools:list:after", ({ ctx, tools }) => {
  if (ctx.user.environment !== "prod") {
    return tools;
  }

  return tools?.filter((tool) => !tool.name.includes("__experimental"));
});
```

<Tip>
  Keep visibility logic in one place. It makes production and staging tool lists easier to audit.
</Tip>

## Proxy Resources, Prompts, and Completions

Fentaris proxies MCP resources, resource templates, prompts, and argument completion when at least one upstream transport supports those methods.

Prompts use the same namespace format as tools:

```txt theme={"theme":{"light":"github-light","dark":"github-dark"}}
github__summarize_issue
```

Resources and resource templates use Fentaris-owned URIs:

```txt theme={"theme":{"light":"github-light","dark":"github-dark"}}
fentaris://resources/github/file%3A%2F%2F%2Fissues%2F123.md
fentaris://resource-templates/github/file%3A%2F%2F%2Fissues%2F%7Bid%7D.md
```

Clients should pass these values back unchanged to `resources/read`, `prompts/get`, and `completion/complete`.

## Add Local Capabilities

Use `app.local(name)` when the Fentaris app itself should expose MCP tools, resources, prompts, or completions beside upstream MCP servers.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { fentaris, mcp, policy, stdio } from "@fentaris/core";

const app = fentaris({
  policy: policy("dev")
    .mcp("github")
    .allow("search_issues")
    .mcp("workspace")
    .allow("status")
    .mcp("workspace")
    .allowCapability({ operation: "resource:read", target: "config://current", targetKind: "resource" }),
  servers: [
    mcp("github", {
      transport: stdio({ command: "github-mcp-server" }),
    }),
  ],
});

app.local("workspace")
  .tool("status", { inputSchema: { type: "object" } }, async (ctx) => ({
    content: [{ type: "text", text: `subject=${ctx.subject?.id ?? "anonymous"}` }],
  }))
  .resource("config://current", { name: "Current config" }, async () => ({
    contents: [{ uri: "config://current", text: "{}" }],
  }))
  .prompt("review_pr", { arguments: [{ name: "diff" }] }, async (_ctx, params) => ({
    messages: [{ role: "user", content: { type: "text", text: String(params.arguments?.diff ?? "") } }],
  }));
```

Local tools and prompts use the same public naming convention as upstream servers, such as `workspace__status` and `workspace__review_pr`. Local resources and resource templates use the same Fentaris proxy URI helpers with `workspace` as the server name.

<Note>
  A local namespace name cannot match an upstream MCP server name. Fentaris reports a configuration diagnostic before serving requests.
</Note>

## Customize Request Logging

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
proxy.use(async (ctx, next) => {
  ctx.log.setTag("subject", ctx.subject?.id ?? "anonymous");
  ctx.log.info("proxy.request");

  return next();
});
```

Use a stable tag such as `traceId`, `user.id`, or `tenantId` so one tool call can be traced across systems.

## Close Cleanly

When the proxy shuts down, close upstream transports to avoid orphaned server processes.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
process.on("SIGTERM", async () => {
  await proxy.close();
  process.exit(0);
});
```

Handle `SIGINT` in local development and `SIGTERM` in production.

## Low-Level API

`new McpProxy(...)`, `new McpServer(...)`, and explicit transport constructors remain available for advanced integrations and compatibility. New applications should start with `fentaris(...)`, `mcp(...)`, and transport helpers.

## Operational Checklist

* Keep server names stable across environments.
* Keep `path` stable for client integrations.
* Resolve user, tenant, and trace metadata at the edge.
* Prefer `policy(...)` for durable authorization.
* Use middleware for argument-aware runtime checks.
* Avoid hard-coding secrets; inject them through credential or env configuration.
* Close the proxy on process shutdown.

## Related Documentation

* [Setup](/getting-started/setup)
* [Policies](/concepts/policies)
* [McpProxy reference](/reference/mcp-proxy)
