Keep Fentaris deployments safe with centralized identity, policy, secrets, and audit controls.
Start with allow-list policy, authenticated users, scoped upstream servers, and structured logging. Add runtime middleware only for checks that depend on request data.
Quick Start
Declare the proxy with high-level helpers and keep security rules close to the upstream MCP server they govern.
import { fentaris, group, mcp, policy, stdio, user } from "@fentaris/core";
const productionPolicy = policy("production")
.mcp("filesystem")
.allow("read_file")
.mcp("filesystem")
.deny("write_file")
.mcp("filesystem")
.deny("delete_file");
const proxy = fentaris({
groups: [
group({
id: "readers",
users: [user("alice", { tenantId: "acme" })],
policy: productionPolicy,
}),
],
servers: [
mcp("filesystem", {
transport: stdio({
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/srv/shared"],
}),
}),
],
autoLog: true,
});
Use allow-list policy in production. Deny rules are useful for explicit exceptions, but the baseline should be the smallest set of tools and capabilities each group needs.
Policy should block known destructive tools before runtime middleware runs.
const supportPolicy = policy("support")
.mcp("github")
.allow("search_issues")
.mcp("github")
.allow("create_issue")
.mcp("github")
.deny("delete_repo");
Add middleware when the decision depends on request arguments, tenant state, or a live approval system.
proxy.mcp("github").tool("create_issue", async (ctx, next) => {
if (typeof ctx.args?.title !== "string") {
return ctx.deny("Issue title is required.");
}
ctx.log.info("github.create_issue.validated", {
subject: ctx.subject?.id,
groups: ctx.subject?.groups.map((group) => group.id),
});
return next();
});
Govern All MCP Capabilities
Tools are only one MCP surface. Resources, resource templates, prompts, and completion can expose data or shape model behavior.
const leastPrivilege = policy("least-privilege")
.mcp("filesystem")
.allow("read_file")
.mcp("filesystem")
.allowCapability({
operation: "resources:list",
targetKind: "resource",
})
.mcp("filesystem")
.denyCapability({
operation: "resource:read",
target: "file:///secrets.env",
targetKind: "resource",
});
Use list permissions for discovery and direct-operation permissions such as resource:read, prompt:get, and completion:complete before Fentaris forwards a request upstream.
Scope Servers Per Group
Use groups to make server access explicit. This keeps tool visibility, credentials, and policy tied to the caller.
const proxy = fentaris({
groups: [
group({
id: "support",
users: [user("alice")],
servers: [
mcp("github", {
transport: stdio({ command: "github-mcp-server" }),
}),
],
policy: policy("support")
.mcp("github")
.allow("search_issues")
.mcp("github")
.allow("create_issue"),
}),
],
});
Server-scoped policy also reduces accidental exposure when a new upstream MCP server is added to the proxy.
Compose App-Level Policy And Groups
For incremental proxy setup, declare policies and groups from the app handle before start().
import { fentaris, stdio, user } from "@fentaris/core";
const app = fentaris({ autoLog: true });
app.policy("readonly")
.mcp("github")
.allow("search_issues");
app.policy("maintainers")
.mcp("github")
.allow("*")
.mcp("github")
.deny("delete_repo");
app.group("guests")
.users(user("guest"))
.policy("readonly");
app.group("maintainers")
.users(user("alice"), user("bob"))
.policy("maintainers");
app.mcp("github", {
transport: stdio({ command: "github-mcp-server" }),
});
Fentaris validates the final app before serving. Missing named policies, empty groups, duplicate declarations, and policies that still reference missing upstream MCP servers fail startup with configuration diagnostics.
Protect Secrets
Bind secrets through credentials or environment injection. Do not pass secrets through user-controlled metadata or tool arguments.
import { bearer, credential, credentialEnv, fentaris, group, mcp, policy, streamableHttp, user } from "@fentaris/core";
const proxy = fentaris({
groups: [
group({
id: "maintainers",
users: [user("bob")],
credentials: {
"github.token": credentialEnv("GITHUB_TOKEN"),
},
policy: policy("maintainers").mcp("github").allow("*"),
}),
],
servers: [
mcp("github", {
transport: streamableHttp({ url: "https://github.example/mcp" }),
auth: bearer(credential("github.token")),
}),
],
});
Store production secrets in your infrastructure secret manager and inject them at deploy time. Keep .fentaris/credentials.enc.json private when using local encrypted credentials.
Do not forward whole environment maps into HTTP headers. Use auth, resolveHeaders, or an explicit envHeaderMap so token-like variables such as GITHUB_TOKEN are not sent upstream unless you map them deliberately.
Validate Identity
When users declare API keys, Fentaris uses API-key identity by default with the x-fentaris-api-key header. Store API keys as credential sources, such as environment variables or encrypted local credentials, instead of literal strings in code.
For local encrypted credentials, register a downstream client API key with the CLI:
printf '%s' "$ADMIN_API_KEY" | fentaris auth api-key add alice --value-stdin
import { credentialEnv, fentaris, group, mcp, policy, stdio, user } from "@fentaris/core";
const proxy = fentaris({
groups: [
group({
id: "admins",
users: [
user("alice", {
apiKeys: [credentialEnv("ADMIN_API_KEY")],
}),
],
policy: policy("admins").mcp("*").allow("*"),
}),
],
servers: [
mcp("admin", {
transport: stdio({ command: "admin-mcp-server" }),
}),
],
});
Trusted identity headers should only be used behind an internal gateway that already authenticated the caller. Do not expose direct user-id headers to untrusted clients.
Audit Decisions
Enable autoLog and add stable tags for traceability. Logs should include metadata, not raw payloads or secret values.
const proxy = fentaris({
servers: [
mcp("github", {
transport: stdio({ command: "github-mcp-server" }),
}),
],
autoLog: true,
});
proxy.use(async (ctx, next) => {
ctx.log.setTag("subject", ctx.subject?.id ?? "anonymous");
ctx.log.setTag("tenant", String(ctx.subject?.tenant?.id ?? "unknown"));
return next();
});
Review denied calls regularly. They show missing policy, unexpected client behavior, or attempted abuse.
Harden Network Exposure
Run public Fentaris deployments behind a reverse proxy or cloud load balancer.
- Terminate TLS at the edge and forward only HTTPS traffic.
- Restrict inbound access with private networks or IP allow-lists.
- Keep upstream MCP servers private when possible.
- Rotate API keys and upstream credentials regularly.
Low-Level API
The class constructors remain available for compatibility and advanced embedding. Prefer fentaris(...), mcp(...), stdio(...), streamableHttp(...), group(...), user(...), and policy(...) for new applications.
import { McpProxy, Policy } from "@fentaris/core";
const legacyPolicy = new Policy({ name: "legacy" })
.mcp("filesystem")
.allow("read_file");
const proxy = new McpProxy({
policy: legacyPolicy,
servers: [filesystemServer],
});