# @frontmcp/sdk reference

> The decorators that declare what a FrontMCP server exposes, the context members your code calls inside a tool, resource or prompt, and the entry points that run a server over HTTP, in a Worker or in your own process.

Source: https://frontmcp.dev/reference/sdk

`@frontmcp/sdk` is the package a FrontMCP server is written with. Its pages fall into three groups. **Decorators** declare what the server exposes, from `@FrontMcp` and `@App` down to `@Tool`, `@Resource` and `@Prompt`. **Context members** are what `this` offers inside `execute()`, like `this.auth`, `this.get()` and `this.fail()`. **Entry points** run the same classes over HTTP, in a Worker or another web-standard runtime, or inside your own process. Limits, errors, the scope and the `z` you write schemas with complete the section. This page is for looking things up; [Your First Tool](https://frontmcp.dev/learn/your-first-tool) teaches the same pieces in order.

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

---

## Decorators

Decorators declare what your server exposes.

| Page | Declares |
| --- | --- |
| [`@FrontMcp`](https://frontmcp.dev/reference/sdk/frontmcp) | An MCP server, the apps it hosts, and the settings that apply to all of them |
| [`@App`](https://frontmcp.dev/reference/sdk/app) | Related tools, resources, prompts and providers, grouped into an app the server hosts |
| [`@Tool`](https://frontmcp.dev/reference/sdk/tool) | A function an AI model can call, with validated input and optional structured output |
| [`@Resource`](https://frontmcp.dev/reference/sdk/resource) | Data at a fixed URI that clients can list and read, like a policy document |
| [`@ResourceTemplate`](https://frontmcp.dev/reference/sdk/resource-template) | A family of resources whose URIs follow a pattern, like `ticket://{id}` |
| [`@Prompt`](https://frontmcp.dev/reference/sdk/prompt) | A reusable message template that users pick in their client and fill in with arguments |
| [`@Provider`](https://frontmcp.dev/reference/sdk/provider) | A shared service, like a database client or a cache, that tools, resources and prompts get with `this.get()` |
| [`@Job`](https://frontmcp.dev/reference/sdk/job) | A unit of work with validated input that clients run by name, while they wait or in the background, with retries |
| [`@Workflow`](https://frontmcp.dev/reference/sdk/workflow) | Jobs chained into steps that run in order or in parallel, each step's input built from the results before it |
| [`@Agent`](https://frontmcp.dev/reference/sdk/agent) | A tool that runs its own model loop, with its own instructions and tools, so a client's model can hand it a whole task |
| [`@Skill`](https://frontmcp.dev/reference/sdk/skill) | A written procedure, with the tools it uses, that clients find and load for their model to follow |
| [`@Plugin`](https://frontmcp.dev/reference/sdk/plugin) | Providers, tools, resources, prompts, skills and hooks, packaged so an app or a whole server turns them on with one line |
| [`@Adapter`](https://frontmcp.dev/reference/sdk/adapter) | Tools, resources and prompts built from another source when the server starts, like a spec or a list of saved views, and replaced while it runs |
| [`@Channel`](https://frontmcp.dev/reference/sdk/channel) | Events pushed into a connected Claude Code session as they happen |
| [Hook decorators](https://frontmcp.dev/reference/sdk/hooks) | `Will`, `Did`, `Around` and `Stage`: code that runs before, after, around or inside a stage of every request |

## Context

Inside `execute()`, `this` is the call's context. [Context classes](https://frontmcp.dev/reference/sdk/contexts) lists what `this` is in a tool, resource, prompt, agent, job, channel or skill, and every member each one has. The members used most have pages of their own:

| Page | Does |
| --- | --- |
| [`this.auth`](https://frontmcp.dev/reference/sdk/auth) | Who is calling, with their scopes, roles and claims, and what each auth mode and entry point fills in |
| [`this.context`](https://frontmcp.dev/reference/sdk/context) | The request a call belongs to: its id, trace, auth info and HTTP metadata, and a store that lasts for the request |
| [`this.elicit()`](https://frontmcp.dev/reference/sdk/elicit) | Asks the user for input in the middle of a tool call, and continues with their answer as typed data |
| [`this.progress()`](https://frontmcp.dev/reference/sdk/progress) | Tells the client how far a long call has got, when the client asked for progress |
| [`this.notify()`](https://frontmcp.dev/reference/sdk/notify) | Sends log messages to the client while a tool runs, at the levels the client asked for |
| [`this.get()`](https://frontmcp.dev/reference/sdk/get) | Gets a provider, like a ticket store or your configuration |
| [`this.fetch()`](https://frontmcp.dev/reference/sdk/fetch) | Calls another HTTP service, with the request's tracing headers and a default timeout |
| [`this.fail()`](https://frontmcp.dev/reference/sdk/fail) | Ends a tool call with an error the model can read, carrying the code you choose |
| [`this.callTool()`](https://frontmcp.dev/reference/sdk/call-tool) | Calls another tool of the same server, as the same caller |
| [`this.respond()`](https://frontmcp.dev/reference/sdk/respond) | Ends a call early with a result you built yourself, or answers a call from a hook |

## Running a server

Importing a class decorated with `@FrontMcp` starts FrontMCP's HTTP server. These run the same configuration in other ways:

| Page | Does |
| --- | --- |
| [`FrontMcpInstance`](https://frontmcp.dev/reference/sdk/frontmcp-instance) | Builds and runs a server from its `@FrontMcp` configuration: over HTTP, stdio or a Unix socket, as a serverless handler, or in-process |
| [`createFetchHandler()`](https://frontmcp.dev/reference/sdk/create-fetch-handler) | Serves MCP from any runtime that speaks web `Request` and `Response`, like Cloudflare Workers, Deno, Bun or a framework's route |
| [`create()` and `createDirect()`](https://frontmcp.dev/reference/sdk/create) | A server inside your own process, whose tools, resources, prompts and jobs you call as async functions, as a user you name |
| [`server.registerTool()`](https://frontmcp.dev/reference/sdk/register-tool) | Adds a tool to a running `create()` or `createDirect()` server from code outside it, and removes it again |
| [`connect()` and client adapters](https://frontmcp.dev/reference/sdk/connect) | An MCP client connected in memory, with the server's tools and results in the shape Claude, OpenAI, LangChain or the Vercel AI SDK expects |

## Limits, errors and the scope

| Page | Covers |
| --- | --- |
| [Guard options](https://frontmcp.dev/reference/sdk/guard) | A tool's `rateLimit`, `concurrency` and `timeout`, `@FrontMcp`'s `throttle`, whose calls each limit counts, and the error each one produces |
| [Error classes](https://frontmcp.dev/reference/sdk/error-classes) | The SDK's error classes by area, the code a client receives, whether the message survives production, and whether you throw it or FrontMCP does |
| [Error codes](https://frontmcp.dev/reference/errors) | What FrontMCP's JSON-RPC error codes and tool error results mean, and how to fix them |
| [Scope and registries](https://frontmcp.dev/reference/sdk/scope) | `this.scope`: the registries you can list and search, what you can add at runtime, and which parts are FrontMCP's own machinery |

## `z`, `eagerZ` and `lazyZ`

Every example on this site imports `z` from `@frontmcp/sdk`, not from `zod`. It's Zod 4's `z` with one change: `z.object()`, `z.strictObject()`, `z.looseObject()`, `z.union()`, `z.discriminatedUnion()`, `z.intersection()`, `z.record()` and `z.tuple()` return a stand-in, and the real Zod schema is built the first time the schema is used, by a parse or by reading it, like `.shape`. Methods you chain on a stand-in, like `.optional()` and `.describe()`, return stand-ins too. So loading and starting a server doesn't build the objects in its tools' `inputSchema`: they're built when a client first lists the tools or calls one. The other factories, like `z.string()` and `z.enum()`, are Zod's own.

| Export | What it is |
| --- | --- |
| `z` | Zod 4's `z`, with the factories above deferred. Use it for every schema in a FrontMCP server. |
| `eagerZ` | Zod's own `z`, the same object `import { z } from "zod"` gives. Its schemas are built when you call it. |
| `lazyZ(build)` | A schema that calls `build()` the first time it's used, and only then. For a schema that's costly to make and may never be used, like one converted from a JSON Schema. |
| `isLazy(schema)` | `true` for a stand-in, from `z`'s deferred factories or from `lazyZ()`. |
| `forceMaterialize(schema)` | The real Zod schema, with every stand-in inside it built. |
| `toJSONSchema(schema)` | Zod's JSON Schema converter, run on `forceMaterialize(schema)`, so it takes any `z` schema. |

A stand-in behaves like the schema it stands for: it parses, `instanceof ZodObject` is `true`, and `z.infer` gives the same type. The SDK exports Zod's classes, like `ZodObject` and `ZodError`, and its types, so a FrontMCP server needs no import from `zod`. Two things to know:

- **Zod's own `toJSONSchema()`** fails on some `z` schemas. `z.toJSONSchema()`, or the function imported from `zod`, throws `TypeError: Cannot set properties of undefined (setting 'ref')` when an object or a union in the schema is `.optional()` or has a `.default()`, like the optional `customer` below. Convert with `toJSONSchema` from `@frontmcp/sdk`, or build that schema with `eagerZ`.
- **Schemas from `zod`** work in a tool's `inputSchema` like the SDK's, as long as they're Zod 4: `frontmcp create` adds `zod` `^4.0.0` to a new project. Schemas from Zod 3, `zod@3` or `zod/v3`, aren't recognized: the tool is listed with no properties, and every call fails with `INVALID_INPUT` and `Unknown error occurred when trying to parse input`.

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

export const TicketInput = {
  title: z.string().min(5).describe("One-line summary of the problem"),
  customer: z.object({ email: z.string().email() }).optional().describe("Who reported it, if known"),
};

@Tool({ name: "create_ticket", description: "Open a new support ticket", inputSchema: TicketInput })
export class CreateTicket extends ToolContext {
  async execute(input: { title: string; customer?: { email: string } }) {
    return { id: "T-4", status: "open", ...input };
  }
}
```

```ts schemas.test.ts
import { test, expect } from "@frontmcp/testing";
import { eagerZ, isLazy, lazyZ, toJSONSchema, z, ZodObject } from "@frontmcp/sdk";
import { TicketInput } from "./create-ticket.tool";

test("`z.object()` is a stand-in that behaves like the schema", () => {
  const customer = z.object({ email: z.string().email() });
  expect(isLazy(customer)).toBe(true);
  expect(isLazy(z.string())).toBe(false);
  expect(customer instanceof ZodObject).toBe(true);
  expect(customer.safeParse({ email: "dana" }).success).toBe(false);
});

test("`eagerZ` is Zod's own `z`", () => {
  expect(isLazy(eagerZ.object({ email: eagerZ.string() }))).toBe(false);
});

test("`lazyZ()` builds the schema once, the first time it's used", () => {
  let builds = 0;
  const imported = lazyZ(() => {
    builds++;
    return eagerZ.object({ id: eagerZ.string() });
  });
  expect(builds).toBe(0);
  imported.parse({ id: "T-1" });
  imported.parse({ id: "T-2" });
  expect(builds).toBe(1);
});

test("Zod's own `toJSONSchema()` fails on an optional object; the SDK's doesn't", () => {
  const schema = z.object(TicketInput);
  expect(() => z.toJSONSchema(schema)).toThrow("Cannot set properties of undefined (setting 'ref')");
  const json = toJSONSchema(schema) as { required: string[] };
  expect(json.required).toEqual(["title"]);
  const eager = eagerZ.object({ customer: eagerZ.object({ email: eagerZ.string() }).optional() });
  expect(() => eagerZ.toJSONSchema(eager)).not.toThrow();
});

test("FrontMCP lists the tool's schema either way", async ({ mcp }) => {
  const tool = (await mcp.tools.list()).find((t: { name: string }) => t.name === "create_ticket");
  expect(tool?.inputSchema.required).toEqual(["title"]);
  expect(tool?.inputSchema.properties.customer.properties.email.format).toBe("email");
});
```

## Which page for which task

| You want to | Read |
| --- | --- |
| Give the model something to call | [`@Tool`](https://frontmcp.dev/reference/sdk/tool) |
| Share a database client or an API client between tools | [`@Provider`](https://frontmcp.dev/reference/sdk/provider) and [`this.get()`](https://frontmcp.dev/reference/sdk/get) |
| Refuse a call with a message the model can act on | [`this.fail()`](https://frontmcp.dev/reference/sdk/fail) and [Error classes](https://frontmcp.dev/reference/sdk/error-classes) |
| Check who is calling before you act | [`this.auth`](https://frontmcp.dev/reference/sdk/auth) |
| Confirm with the user before a tool acts | [`this.elicit()`](https://frontmcp.dev/reference/sdk/elicit) |
| Run work that takes too long for one tool call | [`@Job`](https://frontmcp.dev/reference/sdk/job) |
| Let a model on your server carry out a whole task | [`@Agent`](https://frontmcp.dev/reference/sdk/agent) |
| Run code around every call, like logging or auditing | [Hook decorators](https://frontmcp.dev/reference/sdk/hooks), packaged as a [`@Plugin`](https://frontmcp.dev/reference/sdk/plugin) |
| Generate tools from a spec, a catalog or a list kept somewhere else | [`@Adapter`](https://frontmcp.dev/reference/sdk/adapter), or the [OpenAPI adapter](https://frontmcp.dev/reference/adapters/openapi) for an OpenAPI spec |
| Serve from Cloudflare Workers, Deno or Bun | [`createFetchHandler()`](https://frontmcp.dev/reference/sdk/create-fetch-handler) |
| Call your tools from a script or a test, without HTTP | [`create()` and `createDirect()`](https://frontmcp.dev/reference/sdk/create) |
| Give your tools to an agent loop built on Claude, OpenAI, LangChain or the Vercel AI SDK | [`connect()` and client adapters](https://frontmcp.dev/reference/sdk/connect) |
| Cap how often a tool runs, or how long it may take | [Guard options](https://frontmcp.dev/reference/sdk/guard) |
| Find out what an error code means | [Error codes](https://frontmcp.dev/reference/errors) |
| Hand a schema to code that calls Zod's own functions | [`z`, `eagerZ` and `lazyZ`](#z-eagerz-and-lazyz) |

## The three groups in one server

A decorator for each part of the server, context members inside the tool, and the **Tests** tab running the same app through a second entry point, `createDirect()`, in-process and as a named user:

```ts help-desk.app.ts active
import { App, Provider, PublicMcpError, Tool, ToolContext, z } from "@frontmcp/sdk";

@Provider({ name: "TicketStore" })
export class TicketStore {
  private tickets = [
    { id: "T-1", title: "Cannot log in", status: "open" },
    { id: "T-2", title: "Invoice total is wrong", status: "closed" },
  ];
  find(id: string) {
    return this.tickets.find((t) => t.id === id);
  }
}

@Tool({
  name: "get_ticket",
  description: "Get one support ticket by its id",
  inputSchema: { id: z.string().describe("Ticket id, like T-1") },
})
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    const ticket = this.get(TicketStore).find(id); // a provider
    if (!ticket) this.fail(new PublicMcpError(`There's no ticket ${id}.`, "NOT_FOUND")); // an error the model reads
    return { ticket, caller: this.auth.user.sub }; // who is calling
  }
}

@App({ id: "help-desk", name: "Help Desk", tools: [GetTicket], providers: [TicketStore] })
export class HelpDeskApp {}
```

```ts main.ts
import { FrontMcp } from "@frontmcp/sdk";
import { HelpDeskApp } from "./help-desk.app";

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

```ts help-desk.test.ts
import { test, expect } from "@frontmcp/testing";
import { FrontMcpInstance } from "@frontmcp/sdk";
import { HelpDeskApp } from "./help-desk.app";

test("the tool gets the store with `this.get()`", async ({ mcp }) => {
  const result = await mcp.tools.call("get_ticket", { id: "T-1" });
  expect(result).toBeSuccessful();
  expect(result.json().ticket).toEqual({ id: "T-1", title: "Cannot log in", status: "open" });
});

test("`this.fail()` sends an error the model can read", async ({ mcp }) => {
  const result = await mcp.tools.call("get_ticket", { id: "T-9" });
  expect(result).toBeError("NOT_FOUND");
  expect(result).toHaveTextContent("There's no ticket T-9.");
});

test("over HTTP with no credentials, `this.auth` is an anonymous caller", async ({ mcp }) => {
  const result = await mcp.tools.call("get_ticket", { id: "T-1" });
  expect(result.json().caller).toMatch(/^anon:/);
});

test("`createDirect()` runs the same app in-process, as the user you name", async () => {
  const server = await FrontMcpInstance.createDirect({ info: { name: "help-desk", version: "1.0.0" }, apps: [HelpDeskApp] });
  try {
    const result = await server.callTool("get_ticket", { id: "T-2" }, { authContext: { user: { sub: "agent-7" } } });
    expect(result.structuredContent).toMatchObject({ ticket: { id: "T-2", status: "closed" }, caller: "agent-7" });
  } finally {
    await server.dispose();
  }
});
```

## Related pages

- [Server configuration](https://frontmcp.dev/reference/server): apps, configuration files, logging, observability and flows, across decorators.
- [Authentication and authorization](https://frontmcp.dev/reference/auth): the `auth` and `authorities` options, and what fills in `this.auth`.
- [Plugins and adapters](https://frontmcp.dev/reference/plugins): the official plugins and the OpenAPI adapter.
- [`@frontmcp/testing`](https://frontmcp.dev/reference/testing): end-to-end tests that call a server like a client.
- Learn: [Describing Capabilities](https://frontmcp.dev/learn/describing-capabilities), [Structuring a Server](https://frontmcp.dev/learn/structuring-a-server) and [Running FrontMCP Anywhere](https://frontmcp.dev/learn/running-frontmcp-anywhere).
