# Securing a Server

> Who may connect to a FrontMCP server, what each caller may do, and how often and how long a tool may run. Authentication, authorization, rate limits and timeouts.

Source: https://frontmcp.dev/learn/securing-a-server

Every server so far in this course has let anyone call any tool, as often as they liked, for as long as the tool took. That's fine on your laptop. Once a server is reachable over a network, you need answers to three questions: who is calling, what may they do, and how much of it. This chapter answers each one with FrontMCP.

**In this chapter**
- [How clients prove who they are, and which auth mode to choose](https://frontmcp.dev/learn/authenticating-clients)
- [How to decide who can call which tool, and refuse the rest](https://frontmcp.dev/learn/authorizing-calls)
- [How to limit how often a tool runs, how many run at once, and for how long](https://frontmcp.dev/learn/limiting-calls)

## Authenticating clients

A client proves who it is with a credential in the `Authorization` header of every request, and the server's `auth` mode decides which credentials count. With no `auth` at all, a server is public: every request gets in, as an anonymous caller.

```ts whoami.tool.ts
import { Tool, ToolContext } from "@frontmcp/sdk";

@Tool({
  name: "whoami",
  description: "Say who the server thinks is calling, and with which scopes.",
  inputSchema: {},
  annotations: { readOnlyHint: true },
})
export class WhoAmI extends ToolContext {
  async execute() {
    return { caller: this.auth.user.sub, scopes: this.auth.scopes };
  }
}
```

```ts whoami.test.ts hidden
import { test, expect } from "@frontmcp/testing";

test("each call is a new anonymous caller", async ({ mcp }) => {
  const first = (await mcp.tools.call("whoami", {})).json();
  const second = (await mcp.tools.call("whoami", {})).json();
  expect(first).toMatchObject({ caller: expect.stringMatching(/^anon:/), scopes: ["anonymous"] });
  expect(second.caller).not.toBe(first.caller);
});
```

Call it again and the id changes: under MCP 2026-07-28, each anonymous request is a new caller. One line of configuration requires a shared key instead, and other modes accept tokens from your identity provider or sign users in themselves:

```ts
auth: { mode: "static", tokens: [process.env.DESK_API_KEY!] },
```

Read more: [Authenticating Clients](https://frontmcp.dev/learn/authenticating-clients)
Learn where credentials travel, how the `public`, `static`, `transparent`, `local` and `remote` modes differ, what a client gets back when it isn't let in, and how to choose.

## Deciding who can call what

Knowing who is calling isn't the same as deciding what they may do. A tool can read the caller from `this.auth` and refuse, with a message the model can pass on to the user. Here only callers with the `tickets:write` scope may close tickets, and an anonymous caller doesn't have it:

```ts close-ticket.tool.ts
import { PublicMcpError, Tool, ToolContext, z } from "@frontmcp/sdk";

@Tool({
  name: "close_ticket",
  description: "Close a support ticket. Only signed-in support agents can close tickets.",
  inputSchema: { id: z.string().describe("Ticket id, like T-1") },
  annotations: { destructiveHint: true },
})
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    if (!this.auth.hasScope("tickets:write")) {
      this.fail(new PublicMcpError("Only signed-in support agents can close tickets. Tell the user to sign in as an agent.", "FORBIDDEN"));
    }
    return { id, closed: true };
  }
}
```

A tool can also declare who may use it with `authorities`. FrontMCP then refuses other callers before `execute()` runs, and leaves the tool out of their `tools/list`.

Read more: [Deciding Who Can Call What](https://frontmcp.dev/learn/authorizing-calls)
Learn what `this.auth` holds in each auth mode, how to refuse a call well, how to declare roles and permissions with `authorities`, and why `visibility: "hidden"` doesn't protect a tool.

## Limiting and timing out calls

A model can call a tool in a loop. `rateLimit`, `concurrency` and `timeout` on `@Tool` cap how often it runs, how many calls run at once, and how long one may take. This export runs at most twice an hour: it ran once when the example started, so press **Call** twice more.

```ts main.ts
import { App, FrontMcp, Tool, ToolContext } from "@frontmcp/sdk";

@Tool({
  name: "export_tickets",
  description: "Export every support ticket as CSV. At most 2 exports an hour.",
  inputSchema: {},
  annotations: { readOnlyHint: true },
  rateLimit: { maxRequests: 2, windowMs: 60 * 60_000 },
})
class ExportTickets extends ToolContext {
  async execute() {
    return { csv: "id,title,status\nT-1,Cannot log in,open\nT-2,Invoice total is wrong,closed" };
  }
}

@App({ id: "help-desk", name: "Help Desk", tools: [ExportTickets] })
class HelpDeskApp {}

@FrontMcp({
  info: { name: "help-desk", version: "1.0.0" },
  apps: [HelpDeskApp],
})
export default class Server {}
```

```ts limit.test.ts hidden
import { test, expect } from "@frontmcp/testing";

test("the third export in an hour is refused", async ({ mcp }) => {
  await mcp.tools.call("export_tickets", {});
  await mcp.tools.call("export_tickets", {});
  const third = await mcp.tools.call("export_tickets", {});
  expect(third.raw._meta?.code).toBe("RATE_LIMIT_EXCEEDED");
  expect(third.text()).toMatch(/Retry after \d+ seconds/);
});
```

The third call is refused with `RATE_LIMIT_EXCEEDED` and says when to retry.

Read more: [Limiting and Timing Out Calls](https://frontmcp.dev/learn/limiting-calls)
Learn what callers get back when a limit trips, whose calls a limit counts, how to stop a tool's runs overlapping, what happens when a call runs out of time, and how to choose limits for expensive and destructive tools.

## What's next?

Start the chapter with [Authenticating Clients](https://frontmcp.dev/learn/authenticating-clients). After it, [Doing Work in the Background](https://frontmcp.dev/learn/background-work) covers work that's too slow, too flaky or too long for a single tool call.
