# FrontMcpInstance

> The class that builds and runs a server from its @FrontMcp configuration, over HTTP, stdio, a Unix socket, a serverless handler or in-process.

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

`FrontMcpInstance` builds a server from the configuration you give [`@FrontMcp`](https://frontmcp.dev/reference/sdk/frontmcp) and runs it. You rarely call it: importing a class decorated with `@FrontMcp` calls `FrontMcpInstance.bootstrap()`, which builds the server and starts an HTTP server. Its other static methods run the same configuration in other ways: over stdio, on a Unix socket, as a handler for a serverless platform or a Worker, or [in your own process](https://frontmcp.dev/reference/sdk/create).

```ts
await FrontMcpInstance.bootstrap(config)
```

---

## Reference

### `FrontMcpInstance.bootstrap(config)`

Call `bootstrap()` to start the HTTP server yourself, for example after connecting to a database. Set `serve: false` on `@FrontMcp`, so importing the class doesn't start a second server, and pass the same configuration:

```ts main.ts
import "reflect-metadata";
import { FrontMcp, FrontMcpInstance } from "@frontmcp/sdk";
import { config } from "./config";
import { connectToDatabase } from "./db";

@FrontMcp({ ...config, serve: false })
export default class HelpDeskServer {}

await connectToDatabase();
await FrontMcpInstance.bootstrap(config);
```

[See more examples below.](#usage)

Starting a server needs a real port and a Node process, so the Playground can't run `bootstrap()`, `createHandler()`, `runStdio()` or `runUnixSocket()`. What this page says about them was checked by running them in Node.

#### Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `config` | `FrontMcpConfigInput` | The object you'd pass to `@FrontMcp`: [`info`, `apps`](https://frontmcp.dev/reference/sdk/frontmcp#options) and the rest. Not the decorated class: a class fails with a `ZodError`, [`Invalid input: expected object, received undefined`](#invalid-input-expected-object-received-undefined). |

#### Returns

A promise that resolves once the server is listening, and logs `MCP HTTP (Express) on 127.0.0.1:3000`. It rejects when the configuration is invalid or the server can't listen, for example `listen EADDRINUSE: address already in use 127.0.0.1:3000` when the port is taken.

`bootstrap()` returns nothing to stop the server with. The process ends it.

### What `@FrontMcp` does on import

When the decorated class is defined, `@FrontMcp` checks the configuration, records it on the class, and then, depending on the environment:

| Environment | What happens |
| --- | --- |
| `FRONTMCP_SCHEMA_EXTRACT` set | Nothing more. The configuration is only recorded. |
| `FRONTMCP_STDIO` set | [`runStdio(config)`](#frontmcpinstancerunstdioconfigorclass): serve one client over stdin and stdout. Nothing, with `serve: false`. |
| `FRONTMCP_SERVERLESS` set | [`createHandler(config)`](#frontmcpinstancecreatehandlerconfig), or `createFetchHandler(config)` if `FRONTMCP_WORKER` is set too. The handler is kept for [`getServerlessHandler()`](#getserverlesshandler-and-getserverlesshandlerasync). |
| None of those | `bootstrap(config)`, unless the configuration has `serve: false`. |

A flag counts as set when its value is `1` or `true`; `yes` or `on` don't count. `frontmcp build` relies on these: its Vercel and Lambda entry files set `FRONTMCP_SERVERLESS=1` before importing your server and export the handler from `getServerlessHandlerAsync()`, and its Cloudflare entry sets `FRONTMCP_WORKER=1` as well.

If starting fails, for example because the port is taken, nothing catches the error: Node reports it and the process exits with code 1.

`FRONTMCP_SCHEMA_EXTRACT` is for tools that need your configuration without running your server. `frontmcp build` uses it, and so does this site's Playground, which is why importing an example's `@FrontMcp` class here never opens a port. Read the recorded configuration with `getDecoratorConfig()`:

```ts
import { getDecoratorConfig } from "@frontmcp/sdk";
import HelpDeskServer from "./main";

const config = getDecoratorConfig(HelpDeskServer); // the parsed configuration, or undefined
```

### Static methods

| Method | Returns | Use it to |
| --- | --- | --- |
| `bootstrap(config)` | `Promise<void>` | Start the HTTP server. [See above.](#frontmcpinstancebootstrapconfig) |
| `createFetchHandler(config)` | `Promise<(request, ctx?, env?) => Promise<Response>>` | Serve MCP from a runtime that hands you a web `Request`: Cloudflare Workers, Deno, Bun, a framework's route. See [`createFetchHandler()`](https://frontmcp.dev/reference/sdk/create-fetch-handler). |
| `createHandler(config)` | `Promise<unknown>`, an Express app | Serve MCP from a platform that calls a Node `(req, res)` handler, like Vercel or AWS Lambda. [See below.](#frontmcpinstancecreatehandlerconfig) |
| `createDirect(config, { app }?)` | `Promise<DirectMcpServer>` | Call tools, resources and prompts from your own code, with no transport. `app` picks an app's own endpoint, and a `workerEnv` in `config` sets the [bindings](https://frontmcp.dev/reference/sdk/create#passing-a-workers-bindings) its calls read (since 1.9.4). See [`create()` and `createDirect()`](https://frontmcp.dev/reference/sdk/create). |
| `runStdio(configOrClass)` | `Promise<void>` | Serve one local client over stdin and stdout. [See below.](#frontmcpinstancerunstdioconfigorclass) |
| `runUnixSocket(options)` | `Promise<{ close }>` | Serve over a Unix socket instead of a TCP port. [See below.](#frontmcpinstancerununixsocketoptions) |
| `createForGraph(config)` | `Promise<FrontMcpInstance>` | Build the server without serving it, to inspect it. [See below.](#inspecting-a-server-without-serving-it) |
| `createForCli(config)` | `Promise<FrontMcpInstance>` | The same, for the `frontmcp` CLI's own commands: it skips widget compilation and the event and elicitation stores to start faster. |

To connect an MCP client in memory instead, use [`connect()`](https://frontmcp.dev/reference/sdk/connect). It isn't a method of `FrontMcpInstance`.

Every method except `runStdio()` needs the configuration object. [`connect()`](https://frontmcp.dev/reference/sdk/connect) and `runStdio()` also accept the decorated class.

#### `FrontMcpInstance.createHandler(config)`

Builds the server without listening, and returns the Express app that would have listened, a Node `(req, res)` handler. Export it from the entry file of a platform that calls one:

```ts api/mcp.ts
import { FrontMcpInstance } from "@frontmcp/sdk";
import { config } from "../src/config";

export default await FrontMcpInstance.createHandler(config);
```

It serves the same paths as `bootstrap()`, and works with `http.createServer(handler)` too. With `serve: false` in the configuration there's no Express app to return, and the promise resolves to `undefined`.

#### `FrontMcpInstance.runStdio(configOrClass)`

Serves one client over stdin and stdout, for clients that start your server as a subprocess, like Claude Desktop. It opens no port.

```ts stdio.ts
import { FrontMcpInstance } from "@frontmcp/sdk";
import { config } from "./config";

FrontMcpInstance.runStdio(config);
```

- stdout carries the protocol, so from the moment you call it, `runStdio()` sends `console.log`, `console.info` and `console.debug` to stderr. A `console.log` before the call still writes to stdout and breaks the client.
- The promise resolves once the client is connected, not when it disconnects.
- On `SIGINT` or `SIGTERM` it shuts the server down and exits with code 0, within 5 seconds.
- Callers over stdio are anonymous: `this.auth.isAnonymous` is `true` and `this.auth.user.sub` is `""`.

It accepts the decorated class as well as the configuration. The class must be imported with `FRONTMCP_STDIO=1` already set, or importing it starts an HTTP server first.

#### `FrontMcpInstance.runUnixSocket(options)`

Serves over a Unix socket file instead of a TCP port, so only processes on the same machine that can open the file can connect:

```ts
const handle = await FrontMcpInstance.runUnixSocket({ ...config, socketPath: "/tmp/help-desk.sock" });
```

`options` is the configuration plus `socketPath` and an optional `sqlite`. `bootstrap()` does the same when the `FRONTMCP_DAEMON_SOCKET` environment variable names a socket path. On `SIGINT` or `SIGTERM` it [shuts down](#lifecycle-and-shutdown) like `bootstrap()`: it deletes the socket file, so new clients can't connect, lets requests in flight finish for up to 5 seconds, and exits with code 0.

`handle.close()` runs the same shutdown without exiting: it resolves once requests in flight have finished and the socket file is gone. (Before 1.9.3, `SIGTERM` exited at once and `close()` only deleted the socket file.)

#### `getServerlessHandler()` and `getServerlessHandlerAsync()`

With `FRONTMCP_SERVERLESS=1`, `@FrontMcp` builds a handler instead of listening. Import the decorated class, then get the handler:

```ts
import { getServerlessHandlerAsync } from "@frontmcp/sdk";
import "./main"; // defines the @FrontMcp class

const handler = await getServerlessHandlerAsync();
```

`getServerlessHandlerAsync()` waits until the handler is ready. `getServerlessHandler()` returns it at once, or `null` while it's still being built. Both throw what building threw. Without `FRONTMCP_SERVERLESS`, `getServerlessHandlerAsync()` throws [`Serverless handler is not initialized`](#serverless-handler-is-not-initialized).

### Instance members

`bootstrap()` builds an instance and starts it. `createForGraph()` returns one that's built and not started.

| Member | Description |
| --- | --- |
| `config` | The parsed configuration, with defaults filled in. |
| `getConfig()` | Returns `config`. |
| `ready` | A promise that resolves once the server is built: its apps, tools, resources and the rest. |
| `getScopes()` | The server's [scopes](https://frontmcp.dev/reference/sdk/scope): one for the whole server, or one per app with `splitByApp: true`. |
| `getPrimaryScope()` | The scope that `runStdio()` serves, and `createDirect()` and `connect()` without an `app`: the one holding every app that isn't `standalone`, `root`. With `splitByApp: true`, or when every app is `standalone`, the first scope. |
| `getAppScope(app)` | The scope an app has of its own, served at `<entryPath>/<app id>`: each app's with `splitByApp: true`, a `standalone` app's otherwise. `createDirect()` and `connect()` serve it when given that `app`. Throws `ScopeConfigurationError`, `No endpoint serves app "…" on its own`, for an app without one, naming the apps that have one. New in 1.9.3. |
| `start()` | Starts the HTTP server of a built instance, then runs the callbacks registered with `scope.onServerStarted()`. |
| `initialize()` | Builds the server. The constructor calls it and keeps the promise in `ready`; don't call it again. |

The constructor, `new FrontMcpInstance(config)`, takes a configuration that has already been parsed. Use the static methods, which parse it for you.

### Ports and the entry path

| Setting | Where it comes from |
| --- | --- |
| Port | `http.port`, else the `PORT` [environment variable](https://frontmcp.dev/reference/server/config-files#environment-variables), else `3000`. `PORT` is read when the server's options are parsed, so setting `process.env.PORT` in code before `bootstrap()` works. (Changed in 1.9: before, it was read when `@frontmcp/sdk` was first imported, and a later change had no effect.) |
| Address | `127.0.0.1` unless `http.security.bindAddress` or `FRONTMCP_BIND_ADDRESS` says otherwise. See [`http`](https://frontmcp.dev/reference/sdk/frontmcp#http). |
| MCP endpoint | `http.entryPath`, else the `FRONTMCP_HTTP_ENTRY_PATH` environment variable, else the root, `/`. |
| Health checks | `/healthz`, `/health` and `/readyz`, whatever the entry path. |
| With `splitByApp: true` | Each app at `/<app id>` under the entry path. The root answers 404. |

`frontmcp dev` sets `FRONTMCP_HTTP_ENTRY_PATH` to the path in its own configuration.

### Lifecycle and shutdown

`bootstrap()` goes through these steps:

1. Parses the configuration, and fails on anything invalid.
2. Builds the server: its providers, its logger, then its scopes, which build the apps and their tools, resources, prompts, jobs and plugins. This is `ready`.
3. Starts the HTTP server and waits until it listens.
4. Runs every scope's `onServerStarted()` callbacks, then logs `FrontMCP bootstrap complete`.

On `SIGINT` or `SIGTERM` it shuts the server down: it stops taking new connections, lets requests in flight finish for up to 5 seconds, closes what the server holds (its Redis connections, and a distributed instance's heartbeat), and exits `0`, or `1` when a step fails or the whole shutdown takes more than 10 seconds. See [stopping a Node server](https://frontmcp.dev/reference/deployment/node#stopping). A server on a Unix socket is the exception: it removes the socket file and exits at once, as described above. (Changed in 1.9.2: before, `bootstrap()` installed no signal handlers, and `SIGTERM` ended the process at once.)

#### Caveats

- **Importing a decorated class starts a server.** Keep the configuration in a module of its own, and decorate a class with it only in the entry file, so scripts, tests and workers can import the configuration without starting anything.
- **Only `runStdio()` and `connect()` accept the decorated class.** The other methods need the configuration object. Pass `getDecoratorConfig(Server)` if all you have is the class.
- **With `splitByApp: true`, or a `standalone` app, only `bootstrap()`, `createHandler()` and `createFetchHandler()` serve every app.** `runStdio()` serves the [primary scope](#instance-members) only: the apps that aren't `standalone`, or with `splitByApp: true` the first app. `createDirect()` and `connect()` do too, unless they're given an `app`, which serves that app's own scope. See [Apps, discovery and splitting](https://frontmcp.dev/reference/server/apps). (Changed in 1.9.3: before, `createDirect()` and `connect()` had no `app` option. Changed in 1.9.2: `createFetchHandler()` serves them all; see [its entry paths](https://frontmcp.dev/reference/sdk/create-fetch-handler).)
- Declare one `@FrontMcp` per process. Each `bootstrap()` starts another server, and a second one on the same port fails with `EADDRINUSE`.

---

## Usage

### Starting the server after setup

A server whose tools need a database connection, or secrets fetched at startup, shouldn't accept calls before they're ready. Keep the configuration in its own module, turn off `serve`, and call `bootstrap()` when you're ready:

```ts config.ts
import { HelpDesk } from "./help-desk.app";

export const config = {
  info: { name: "help-desk", version: "1.0.0" },
  apps: [HelpDesk],
  http: { port: 8080, entryPath: "/mcp" },
};
```

```ts main.ts
import "reflect-metadata";
import { FrontMcp, FrontMcpInstance } from "@frontmcp/sdk";
import { config } from "./config";
import { connectToDatabase } from "./db";

@FrontMcp({ ...config, serve: false })
export default class HelpDeskServer {}

try {
  await connectToDatabase();
  await FrontMcpInstance.bootstrap(config);
} catch (err) {
  console.error("help-desk didn't start:", err);
  process.exit(1);
}
```

Clients connect to `http://localhost:8080/mcp`. This needs a real port, so run it locally.

### Reading a server's configuration

`@FrontMcp` records the configuration on the class, with every default filled in. `getDecoratorConfig()` reads it back, and it's what to pass to the methods that don't take the class. In the Playground, `FRONTMCP_SCHEMA_EXTRACT` is set, so defining `HelpDeskServer` records the configuration and starts nothing; the Playground then serves it with `createFetchHandler()`.

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

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

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

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string() } })
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, title: "Cannot log in", status: "open" };
  }
}

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

```ts config.test.ts
import { test, expect } from "@frontmcp/testing";
import { FrontMcpInstance, getDecoratorConfig } from "@frontmcp/sdk";
import HelpDeskServer from "./main";

test("the class carries its configuration, with defaults", () => {
  const config = getDecoratorConfig(HelpDeskServer);
  expect(config).toMatchObject({
    info: { name: "help-desk", version: "1.0.0" },
    serve: true,
    transport: { sessionMode: "stateful", protocol: "legacy" },
  });
});

test("the methods that don't take the class take its configuration", async () => {
  await expect(FrontMcpInstance.createForGraph(HelpDeskServer as never)).rejects.toThrow("Invalid input: expected object, received undefined");
  const instance = await FrontMcpInstance.createForGraph(getDecoratorConfig(HelpDeskServer)!);
  expect(instance.getConfig().info.name).toBe("help-desk");
});
```

### Inspecting a server without serving it

`createForGraph()` builds the whole server, apps, tools and all, and doesn't serve it. Use it to check what a configuration produces: in a test, or in a script that lists every tool. A server has one scope, `root`, holding every app's tools. With `splitByApp: true` it has one scope per app, each at its own path, and a `standalone` app always gets one of its own.

```ts inspect.test.ts active
import { test, expect } from "@frontmcp/testing";
import { FrontMcpInstance } from "@frontmcp/sdk";
import { Billing, BillingApi, HelpDesk } from "./apps";

const info = { name: "help-desk", version: "1.0.0" };

function describe(instance: FrontMcpInstance) {
  return instance.getScopes().map((scope) => ({
    id: scope.id,
    path: scope.routeBase,
    tools: scope.tools.getTools().map((tool) => tool.metadata.name),
  }));
}

test("one scope holds every app's tools", async () => {
  const instance = await FrontMcpInstance.createForGraph({ info, apps: [HelpDesk, Billing] });
  expect(describe(instance)).toEqual([{ id: "root", path: "", tools: ["get_ticket", "refund_invoice"] }]);
});

test("with splitByApp, each app is a scope at its own path", async () => {
  const instance = await FrontMcpInstance.createForGraph({ info, apps: [HelpDesk, Billing], splitByApp: true });
  expect(describe(instance)).toEqual([
    { id: "help-desk", path: "/help-desk", tools: ["get_ticket"] },
    { id: "billing", path: "/billing", tools: ["refund_invoice"] },
  ]);
  expect(instance.getPrimaryScope()?.id).toBe("help-desk");
});

test("a standalone app gets a scope of its own; createDirect() serves the primary one, or that one with `app`", async () => {
  const config = { info, apps: [BillingApi, HelpDesk] };
  const instance = await FrontMcpInstance.createForGraph(config);
  expect(describe(instance)).toEqual([
    { id: "billing-api", path: "/billing-api", tools: ["refund_invoice"] },
    { id: "root", path: "", tools: ["get_ticket"] },
  ]);
  expect(instance.getPrimaryScope()?.id).toBe("root");
  expect(instance.getAppScope("billing-api").routeBase).toBe("/billing-api");
  expect(() => instance.getAppScope("help-desk")).toThrow('No endpoint serves app "help-desk" on its own');

  const server = await FrontMcpInstance.createDirect(config);
  const { tools } = await server.listTools();
  await server.dispose();
  expect(tools.map((tool) => tool.name)).toEqual(["get_ticket"]);

  const billing = await FrontMcpInstance.createDirect(config, { app: "billing-api" });
  const billingTools = await billing.listTools();
  await billing.dispose();
  expect(billingTools.tools.map((tool) => tool.name)).toEqual(["refund_invoice"]);
});
```

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

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string() } })
class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "open" };
  }
}

@Tool({ name: "refund_invoice", description: "Refund an invoice", inputSchema: { invoice: z.string() } })
class RefundInvoice extends ToolContext {
  async execute({ invoice }: { invoice: string }) {
    return { invoice, refunded: true };
  }
}

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

@App({ id: "billing", name: "Billing", tools: [RefundInvoice] })
export class Billing {}

@App({ id: "billing-api", name: "Billing API", tools: [RefundInvoice], standalone: true })
export class BillingApi {}
```

A scope's registries are described in [Scope and registries](https://frontmcp.dev/reference/sdk/scope).

### Serving over stdio

Local clients like Claude Desktop and Cursor can start your server as a subprocess and talk to it over stdin and stdout. Give them an entry file that calls `runStdio()` with the configuration, and never imports the decorated class:

```ts stdio.ts
import "reflect-metadata";
import { FrontMcpInstance } from "@frontmcp/sdk";
import { config } from "./config";

FrontMcpInstance.runStdio(config);
```

```json claude_desktop_config.json
{
  "mcpServers": {
    "help-desk": { "command": "node", "args": ["/abs/path/dist/stdio.js"] }
  }
}
```

Or keep one entry file and set `FRONTMCP_STDIO=1` in the client's configuration (`"env": { "FRONTMCP_STDIO": "1" }`): `@FrontMcp` then calls `runStdio()` instead of starting an HTTP server. Anything the server writes to stdout that isn't a protocol message breaks the client, so log to stderr.

---

## Troubleshooting

### `Invalid input: expected object, received undefined`

The error is a `ZodError` with `invalid_union`, whose issues are at `info` and at `apps` (`expected array, received undefined`). You passed the decorated class to a method that takes the configuration object: `bootstrap()`, `createFetchHandler()`, `createDirect()` or `createForGraph()`. The class has no `info` or `apps` of its own: they're recorded on it. (Changed in 1.9: before, the issue was `expected object, received function`.)

```ts
// 🚩 The class
await FrontMcpInstance.createDirect(HelpDeskServer);

// ✅ Its configuration
await FrontMcpInstance.createDirect(getDecoratorConfig(HelpDeskServer)!);
```

Better, export the configuration from a module of its own and pass that. [The first Playground](#reading-a-servers-configuration) shows both.

### `listen EADDRINUSE: address already in use 127.0.0.1:3000`

Something already listens on the port: another copy of your server, or a second `bootstrap()` in the same process, often because a script imported the decorated class and then called `bootstrap()` too. Stop the other process, or choose another port with `http.port` or the `PORT` environment variable. Set `PORT` in the environment that starts the process, or in code before the server starts.

### A script that imports my server starts listening

Importing a class decorated with `@FrontMcp` starts a server. Import the configuration module instead, or set `serve: false` on the decorator and start the server where you want it with `bootstrap()`.

### `Serverless handler is not initialized`

`getServerlessHandlerAsync()` found no handler, because `FRONTMCP_SERVERLESS` wasn't set to `1` or `true` when the decorated class was defined. Without it, `@FrontMcp` called `bootstrap()` and started an HTTP server instead. Set the variable in the platform's environment, or skip the decorator and call `createHandler(config)` or [`createFetchHandler(config)`](https://frontmcp.dev/reference/sdk/create-fetch-handler) yourself.

### My client gets `404` or `Cannot POST /mcp`

The MCP endpoint is at the root unless `http.entryPath` is set. See [the `@FrontMcp` entry](https://frontmcp.dev/reference/sdk/frontmcp#my-client-gets-404-or-cannot-post-mcp).

### Only my first app is reachable

The server uses `splitByApp: true` and is served by `runStdio()`, or by `createDirect()` or `connect()` without an `app`, which use the first scope only. Pass `{ app }` to `createDirect()` or `connect()` for another app's scope, serve the server with `bootstrap()`, `createHandler()` or `createFetchHandler()`, which give each app its path, or turn `splitByApp` off.
