@frontmcp/sdk reference
@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 teaches the same pieces in order.
import { App, FrontMcp, Tool, ToolContext, z } from "@frontmcp/sdk";
Decorators
Decorators declare what your server exposes.
| Page | Declares |
|---|---|
@FrontMcp | An MCP server, the apps it hosts, and the settings that apply to all of them |
@App | Related tools, resources, prompts and providers, grouped into an app the server hosts |
@Tool | A function an AI model can call, with validated input and optional structured output |
@Resource | Data at a fixed URI that clients can list and read, like a policy document |
@ResourceTemplate | A family of resources whose URIs follow a pattern, like ticket://{id} |
@Prompt | A reusable message template that users pick in their client and fill in with arguments |
@Provider | A shared service, like a database client or a cache, that tools, resources and prompts get with this.get() |
@Job | A unit of work with validated input that clients run by name, while they wait or in the background, with retries |
@Workflow | Jobs chained into steps that run in order or in parallel, each step's input built from the results before it |
@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 | A written procedure, with the tools it uses, that clients find and load for their model to follow |
@Plugin | Providers, tools, resources, prompts, skills and hooks, packaged so an app or a whole server turns them on with one line |
@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 | Events pushed into a connected Claude Code session as they happen |
| Hook decorators | 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 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 | Who is calling, with their scopes, roles and claims, and what each auth mode and entry point fills in |
this.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() | Asks the user for input in the middle of a tool call, and continues with their answer as typed data |
this.progress() | Tells the client how far a long call has got, when the client asked for progress |
this.notify() | Sends log messages to the client while a tool runs, at the levels the client asked for |
this.get() | Gets a provider, like a ticket store or your configuration |
this.fetch() | Calls another HTTP service, with the request's tracing headers and a default timeout |
this.fail() | Ends a tool call with an error the model can read, carrying the code you choose |
this.callTool() | Calls another tool of the same server, as the same caller |
this.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 | Builds and runs a server from its @FrontMcp configuration: over HTTP, stdio or a Unix socket, as a serverless handler, or in-process |
createFetchHandler() | Serves MCP from any runtime that speaks web Request and Response, like Cloudflare Workers, Deno, Bun or a framework's route |
create() and createDirect() | A server inside your own process, whose tools, resources, prompts and jobs you call as async functions, as a user you name |
server.registerTool() | Adds a tool to a running create() or createDirect() server from code outside it, and removes it again |
connect() and client adapters | 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 | A tool's rateLimit, concurrency and timeout, @FrontMcp's throttle, whose calls each limit counts, and the error each one produces |
| 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 | What FrontMCP's JSON-RPC error codes and tool error results mean, and how to fix them |
| Scope and registries | 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 somezschemas.z.toJSONSchema(), or the function imported fromzod, throwsTypeError: Cannot set properties of undefined (setting 'ref')when an object or a union in the schema is.optional()or has a.default(), like the optionalcustomerbelow. Convert withtoJSONSchemafrom@frontmcp/sdk, or build that schema witheagerZ. - Schemas from
zodwork in a tool'sinputSchemalike the SDK's, as long as they're Zod 4:frontmcp createaddszod^4.0.0to a new project. Schemas from Zod 3,zod@3orzod/v3, aren't recognized: the tool is listed with no properties, and every call fails withINVALID_INPUTandUnknown error occurred when trying to parse input.
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 };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Which page for which task
| You want to | Read |
|---|---|
| Give the model something to call | @Tool |
| Share a database client or an API client between tools | @Provider and this.get() |
| Refuse a call with a message the model can act on | this.fail() and Error classes |
| Check who is calling before you act | this.auth |
| Confirm with the user before a tool acts | this.elicit() |
| Run work that takes too long for one tool call | @Job |
| Let a model on your server carry out a whole task | @Agent |
| Run code around every call, like logging or auditing | Hook decorators, packaged as a @Plugin |
| Generate tools from a spec, a catalog or a list kept somewhere else | @Adapter, or the OpenAPI adapter for an OpenAPI spec |
| Serve from Cloudflare Workers, Deno or Bun | createFetchHandler() |
| Call your tools from a script or a test, without HTTP | create() and createDirect() |
| Give your tools to an agent loop built on Claude, OpenAI, LangChain or the Vercel AI SDK | connect() and client adapters |
| Cap how often a tool runs, or how long it may take | Guard options |
| Find out what an error code means | Error codes |
| Hand a schema to code that calls Zod's own functions | 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:
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 {}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Related pages
- Server configuration: apps, configuration files, logging, observability and flows, across decorators.
- Authentication and authorization: the
authandauthoritiesoptions, and what fills inthis.auth. - Plugins and adapters: the official plugins and the OpenAPI adapter.
@frontmcp/testing: end-to-end tests that call a server like a client.- Learn: Describing Capabilities, Structuring a Server and Running FrontMCP Anywhere.