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

# McpProxy

> Reference for the Fentaris proxy API.

> Create and operate the Fentaris proxy runtime

Use `fentaris(...)` for new applications. It validates the same proxy options and returns an `McpProxy` instance that exposes one MCP endpoint and orchestrates upstream MCP servers.

## Quick Start

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

const proxy = fentaris({
  policy: Policy.allowAll(),
  servers: [
    mcp("filesystem", {
      transport: stdio({
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "./demo-files"],
      }),
    }),
  ],
  port: 4000,
  path: "/mcp",
});

await proxy.start();
```

`Policy.allowAll()` is intended for local development and examples. Production proxies should declare least-privilege `policy(...)` or `groups` rules because Fentaris denies tool and capability access by default when no explicit allow matches.

## Options

### `servers`

Array of upstream MCP server declarations.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
servers: [
  mcp("github", {
    transport: stdio({ command: "github-mcp-server" }),
  }),
]
```

### `port`

Default port for `start()`. When omitted, Fentaris reads `port` from the nearest project `fentaris.json`, searching upward from the process current working directory.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
port: 4000
```

### `path`

HTTP path for MCP requests. When omitted, Fentaris reads `path` from the nearest project `fentaris.json`, then falls back to `/mcp`.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
path: "/mcp"
```

**Default:** `/mcp` when no project config is found

### `user`

Static user context or resolver function.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
user: (req) => ({
  id: req.headers["x-user-id"] as string | undefined,
  tenantId: req.headers["x-tenant-id"],
})
```

### `identity`

Identity strategy configuration for proxy-edge auth.

### `policy` and `groups`

Policy and group declarations used to decide which upstream tools, resources, prompts, and completions are visible to each subject.

You can also declare policies and groups after construction with `proxy.policy(...)` and `proxy.group(...)`. See [App-level governance](#app-level-governance).

When no global policy, group policy, or explicit allow-all development policy is configured, tool calls and policy-governed capabilities are denied. Explicit deny rules win over allows from other matching groups.

### Runtime server registration

Policies can reference upstream MCP server names that are registered later through `proxy.server(...)` or `proxy.mcp(...)`, as long as registration happens before `start()` or `listen(...)`.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const proxy = fentaris({
  groups: [
    group({
      id: "limited",
      users: [user("guest")],
      policy: policy("limited").mcp("github").allow("*"),
    }),
  ],
});

proxy.server("github", {
  transport: stdio({ command: "github-mcp-server" }),
});
```

At startup, Fentaris validates the final runtime configuration and fails with `FENTARIS_CONFIG_POLICY_SERVER_NOT_VISIBLE` if a policy still references a missing server.

Use `validateFentarisConfig(config)` when you want a strict static check before composing an app with runtime `proxy.server(...)` calls.

### `logger`, `autoLog`, and `profiler`

Runtime logging and profiler configuration.

### `name` and `version`

MCP server identity surfaced to clients during initialization.

## Methods

### `start(options?, onStarted?)`

Start the HTTP server. Optional values override the configured `port` and `path` for that start call.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
await proxy.start({ port: 4000, path: "/proxy" });
```

### `close()`

Close the HTTP server and all configured upstream transports.

### `listTools(params?, user?)`

Aggregate tool lists across upstream servers and apply Fentaris naming and policy.

Fentaris applies policy filtering before hooks see the upstream list and again after `onListTools` and `tools:list:after` handlers run. Hook-created synthetic tools must use the normal `<server>__<tool>` name and be covered by policy, or they are removed from the final response.

### `callTool(params, user?)`

Call a proxied tool. Fentaris dispatches middleware and hooks, then forwards to the upstream server.

Policy deny decisions are terminal: denied calls return a policy error before call hooks, middleware, routes, or upstream dispatch run. Rate limiters attached to the matching policy permission are enforced automatically before forwarding; manual rate-limit middleware is only needed for custom limits outside policy.

### `listResources(params?, user?)`

Aggregate resources across upstream servers and rewrite each resource `uri` to a Fentaris proxy URI.

### `readResource(params, user?)`

Parse a Fentaris proxy resource URI, restore the upstream resource URI, and read it from the owning server.

### `listResourceTemplates(params?, user?)`

Aggregate resource templates and rewrite each `uriTemplate` to a Fentaris proxy template URI.

### `listPrompts(params?, user?)`

Aggregate prompts and rewrite each prompt `name` to `<server>__<prompt>`.

### `getPrompt(params, user?)`

Parse a proxied prompt name and retrieve the upstream prompt.

### `complete(params, user?)`

Route completion requests for proxied prompt references and proxied resource-template references.

### `local(name)`

Register or retrieve a local MCP capability namespace. Repeating the same name returns the same handle so modules can contribute declarations before startup.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
const workspace = app.local("workspace");

workspace.tool("status", { inputSchema: { type: "object" } }, async (ctx) => ({
  content: [{ type: "text", text: ctx.server?.name ?? "workspace" }],
}));

workspace.resource("config://current", { name: "Current config" }, async () => ({
  contents: [{ uri: "config://current", text: "{}" }],
}));

workspace.resourceTemplate("config://projects/{id}", { name: "Project config" }, async (_ctx, params) => ({
  contents: [{ uri: params.uri, text: "project" }],
}));

workspace.prompt("review_pr", { arguments: [{ name: "diff" }] }, async () => ({
  messages: [],
}));

workspace.completion({ type: "ref/prompt", name: "review_pr" }, async (_ctx, params) => ({
  completion: { values: [params.argument.value] },
}));
```

Local tools and prompts are exposed with the existing proxied naming convention, such as `workspace__status`. Local resources and resource templates are exposed through Fentaris-owned proxy URIs. The namespace name cannot collide with an upstream MCP server name.

## App-Level Governance

Use `proxy.policy(name)` and `proxy.group(id)` when modules contribute governance to one proxy instance or when upstream MCP servers are registered after construction.

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

const app = fentaris();

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

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

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

### `server(name, options?)`

Register or retrieve a scoped upstream MCP server handle. `server(...)` is the app-level alias for `mcp(...)`.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
app.server("filesystem", {
  transport: stdio({
    command: "npx",
    args: ["-y", "@modelcontextprotocol/server-filesystem", "./demo-files"],
  }),
});
```

### `policy(name)`

Register or retrieve a named app-level policy. Repeating the same name returns the same policy instance, so modules can extend one declaration before startup.

### `usePolicy(policyNameOrPolicy)`

Apply a named or concrete policy as the global proxy policy after construction.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
app.policy("demo")
  .mcp("github")
  .allow("search_issues");

app.usePolicy("demo");
```

You can also pass a concrete policy object:

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
app.usePolicy(
  policy("demo").mcp("github").allow("search_issues"),
);
```

<Note>
  `app.usePolicy(name)` requires the named policy to be declared with `app.policy(name)` first. When groups are configured, group policies govern matching users; the global policy is used when no groups are configured.
</Note>

### `group(groupId)`

Return a scoped group handle. Chain `users(...)` and `policy(nameOrPolicy)` to declare the group before `start()` or `listen(...)`.

```ts theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
app.group("maintainers")
  .users(user("alice"))
  .users(user("bob"))
  .policy("maintainers");
```

<Note>
  Fluent groups must include at least one user and a policy before startup. Named policy references must be declared through `app.policy(name)` or passed as a concrete policy instance.
</Note>

Use `app.group(id).server(name)` to scope middleware, operation handlers, and event handlers to subjects in that group and a visible upstream MCP server. `app.group(id).mcp(name)` is kept as an equivalent alias.

Fentaris validates the final configuration before serving. Common diagnostics include `FENTARIS_CONFIG_GROUP_POLICY_UNKNOWN`, `FENTARIS_CONFIG_GROUP_EMPTY_USERS`, `FENTARIS_CONFIG_DUPLICATE_GROUP`, and `FENTARIS_CONFIG_DUPLICATE_POLICY`. See [Troubleshooting](/troubleshooting#app-level-governance-diagnostics).

## Routes and Middleware

Use the returned proxy instance for routes, middleware, and hooks:

```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();
});
```

## Low-Level Constructor

The class constructor is available for advanced integrations that need explicit object construction:

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

const proxy = new McpProxy({
  servers,
  port: 4000,
  path: "/mcp",
});
```

For normal applications, prefer `fentaris(...)`.

## Related Documentation

* [Setup](/getting-started/setup)
* [Proxy setup](/guides/proxy-setup)
* [Governance auth](/guides/governance-auth)
* [McpServer reference](/reference/mcp-server)
