# Nx generators

> Every generator in @frontmcp/nx, with its options, the files it writes, and what its output does: init, workspace, app, lib and server; the thirteen component generators; and the three UI generators.

Source: https://frontmcp.dev/reference/nx/generators

`@frontmcp/nx` has 21 generators. `init` prepares a workspace you already have. The structural ones make projects: a workspace, an app in `apps/`, a library in `libs/`, a deployment shell in `servers/`. The component ones write one class into an existing project, like a tool or a job, with `TODO`s to fill in. The UI ones write React components and HTML shells under `ui/`. Each was run with its defaults and with the options that change its output, against `@frontmcp/sdk` 1.9.2: the projects were built, type-checked and tested with their own targets, the component classes were type-checked and listed in an app that was started, and a server shell's Docker image was built and run. Everything passes in a `frontmcp create --nx` workspace and in a `create-nx-workspace --preset=ts` one. The `server` generator was run again with 1.9.3, and its shells built and ran. Changed in 1.9: apps and libraries come with a test and a `typecheck` target, all four library types type-check, `server` writes deployment files that match the build and a `dev` target, and `ui-shell` installs. Changed in 1.9.2: `lib` names its alias after the workspace's npm scope, a shell's Dockerfile uses the workspace's package manager, and UI packages build in a `--preset=ts` workspace. Changed in 1.9.3: `server` runs your install after it writes the shell, and its Docker, compose and Lambda files match those of `frontmcp create`.

```bash
nx g @frontmcp/nx:<generator> <name> [--project <project>] [options]

nx g @frontmcp/nx:app tickets
nx g @frontmcp/nx:tool search_tickets --project tickets
nx g @frontmcp/nx:server gateway --apps tickets,billing --deploymentTarget node
```

---

## Reference

### Options every generator takes

| Option | Description |
| --- | --- |
| `name` | The first argument. Nx asks for it when it's missing. |
| `--project <project>` | Component generators: the Nx project to write into. Required: without it, `Required property 'project' is missing`; an unknown one, `Project "…" not found in the workspace.` |
| `--directory <dir>` | Component generators: a subfolder of the kind's folder, like `src/tools/<dir>/`. Structural generators: the project's folder, at any depth. |
| `--skipFormat` | Don't run Prettier on the files written. |
| `--dry-run` | Nx's own: list the files without writing them. |
| `--no-interactive` | Nx's own: don't ask for missing options; fail instead. |

Names are turned into a file name and a class name: `search_tickets` writes `search-tickets.tool.ts` with `class SearchTicketsTool`. The name clients see is the name exactly as you typed it, so type it the way you want it listed: `search_tickets`, not `search-tickets`.

No generator registers what it writes. A new tool isn't listed until you add it to its app's `@App({ tools })`, and the same goes for resources, prompts, skills, jobs, workflows and the rest.

### Structural generators

#### `init`

```bash
nx g @frontmcp/nx:init
```

Prepares an existing workspace. `nx add @frontmcp/nx` runs it for you. It adds the FrontMCP and Jest packages to `package.json` and installs them, keeping any version you already have, and adds cacheable `targetDefaults` for the executors to `nx.json`. [Nx plugin](https://frontmcp.dev/reference/nx#nx-add-frontmcpnx-in-an-existing-workspace) lists what it writes. Running it again changes nothing, and it keeps the `targetDefaults` entries it finds.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `--skipPackageJson` | `boolean` | `false` | Don't add dependencies to `package.json`, and install nothing. `nx.json` is still updated. |
| `--skipFormat` | `boolean` | `false` | |

#### `workspace`

```bash
nx g @frontmcp/nx:workspace help-desk-platform --skipInstall
```

Writes a new workspace into `<name>/`, below the current folder. It's what `frontmcp create --nx` runs; [Nx plugin](https://frontmcp.dev/reference/nx#what-frontmcp-create---nx-writes) lists what it writes.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | | The folder and the root `package.json` name. |
| `--packageManager` | `npm`, `yarn`, `pnpm`, `bun` | `npm` | The workspace's package manager, written to `nx.json` as `cli.packageManager`. |
| `--skipInstall` | `boolean` | `false` | Don't install the dependencies. |
| `--skipGit` | `boolean` | `false` | Don't run `git init`. |
| `--createSampleApp` | `boolean` | `true` | Add `apps/demo` with a `hello` tool. `false` leaves `apps/`, `libs/` and `servers/` empty. |

#### `app`

```bash
nx g @frontmcp/nx:app tickets
```

```text
CREATE apps/tickets/jest.config.cjs
CREATE apps/tickets/package.json
CREATE apps/tickets/project.json
CREATE apps/tickets/src/tickets.app.ts
CREATE apps/tickets/src/main.ts
CREATE apps/tickets/src/tools/hello.tool.spec.ts
CREATE apps/tickets/src/tools/hello.tool.ts
CREATE apps/tickets/tsconfig.json
CREATE apps/tickets/tsconfig.lib.json
CREATE apps/tickets/tsconfig.spec.json
```

`src/main.ts` is a `@FrontMcp` server named after the app (`Tickets`), `src/tickets.app.ts` an `@App` with id `tickets` and a `hello` tool, `hello.tool.spec.ts` a unit test of that tool, `package.json` holds the name and version `0.0.1` the CLI names the bundle with, `jest.config.cjs` is what `nx test` runs, and `project.json` has the targets `build`, `dev`, `serve`, `test`, `typecheck` and `inspector` ([Executors](https://frontmcp.dev/reference/nx/executors)), with `cache: true` on `build`, `test` and `typecheck`. `typecheck` runs `tsc --noEmit` on `tsconfig.lib.json` and `tsconfig.spec.json`.

```ts apps/tickets/src/tools/hello.tool.spec.ts
import HelloTool from './hello.tool';

describe('HelloTool', () => {
  it('greets the caller by name', async () => {
    // execute() holds the tool's logic; build the tool without the request context the server injects.
    const tool: HelloTool = Object.create(HelloTool.prototype);

    await expect(tool.execute({ name: 'Ada' })).resolves.toEqual({ message: 'Hello, Ada!' });
  });
});
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | | The project's name, the app's `id`, and the folder name. |
| `--directory` | `string` | `apps/<name>` | The folder, at any depth: `apps/team/billing` gets `extends: "../../../tsconfig.base.json"` and a `$schema` with three `../`. |
| `--tags` | `string` | `scope:apps` | Comma-separated tags for `project.json`. They replace `scope:apps`. |
| `--workspaceRoot` | `string` | | The folder of the Nx workspace when it isn't the tree root. |
| `--skipFormat` | `boolean` | `false` | |

It doesn't add dependencies: [`init`](#init) does that once for the workspace. Its `tsconfig.json` overrides what a TypeScript-solution `tsconfig.base.json` sets, so the app builds in a `create-nx-workspace --preset=ts` workspace too ([Nx plugin](https://frontmcp.dev/reference/nx#adding-the-plugin-to-a-workspace-you-have)).

#### `lib`

```bash
nx g @frontmcp/nx:lib ticket-model --importPath @help-desk/ticket-model
```

```text
CREATE libs/ticket-model/jest.config.cjs
CREATE libs/ticket-model/project.json
CREATE libs/ticket-model/tsconfig.json
CREATE libs/ticket-model/tsconfig.lib.json
CREATE libs/ticket-model/tsconfig.spec.json
CREATE libs/ticket-model/src/ticket-model.spec.ts
CREATE libs/ticket-model/src/ticket-model.ts
CREATE libs/ticket-model/src/index.ts
UPDATE tsconfig.base.json
```

It adds the alias to `tsconfig.base.json`, written from the workspace root (`"@help-desk/ticket-model": ["./libs/ticket-model/src/index.ts"]`), and writes a `project.json` with two targets: `test` (`@frontmcp/nx:test`) and `typecheck`, the same as an app's. A library has no `build`: an app that imports it compiles it into its own bundle ([Monorepo patterns](https://frontmcp.dev/reference/nx/monorepo#sharing-code-in-a-library)). Before 1.9 the `test` target was `@nx/jest:jest`, which `nx add` never installed, and the library got its `build` and `typecheck` from Nx's TypeScript plugin.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | | |
| `--directory` | `string` | `libs/<name>` | |
| `--libType` | `generic`, `plugin`, `adapter`, `tool-register` | `generic` | What `src/` holds, each with a spec beside it: `<name>.ts` with a class; `<name>.plugin.ts`; `<name>.adapter.ts`; or `<name>.tools.ts` with an example tool and a `<Name>Tools` array to spread into an app's `tools`. |
| `--publishable` | `boolean` | `false` | Adds the tag `scope:publishable`, a `package.json` named after `--importPath` (version `0.0.1`, `main` `./dist/index.js`), and a `build` target that runs `tsc -p tsconfig.lib.json` into the library's `dist/`. |
| `--importPath` | `string` | `<scope>/<name>`, else `<name>` | The alias to add, like `@help-desk/schemas`. The default takes the npm scope of the workspace's root `package.json`: `@org/util` in a workspace named `@org/source`. A workspace from `frontmcp create --nx` is named without a scope (`help-desk-platform`), so there the default is the bare name, `ticket-model`. It refuses an alias `tsconfig.base.json` already maps (`The import path "ticket-model" is already mapped in tsconfig.base.json to ./libs/ticket-model/src/index.ts. Pass --importPath with another name.`) and one that names a package the workspace depends on (`The import path "@frontmcp/sdk" is a package this workspace depends on; the library would shadow it.`). Before 1.9.2 the default was `@frontmcp/<name>`, under FrontMCP's own scope. |
| `--tags` | `string` | `scope:libs` | They replace `scope:libs`; `scope:publishable` is added after them. |
| `--skipFormat` | `boolean` | `false` | |

All four types type-check and pass their tests against `@frontmcp/sdk` 1.9.2. `plugin` and `adapter` write the same classes as the component generators [`plugin`](#plugin) and [`adapter`](#adapter); in 1.8.7 they wrote older copies that imported names the SDK doesn't export (`TS2614` on `PluginRegistrationContext` and `AdapterFetchResult`).

#### `server`

```bash
nx g @frontmcp/nx:server gateway --apps demo,tickets
```

```text
CREATE servers/gateway/package.json
CREATE servers/gateway/project.json
CREATE servers/gateway/src/main.ts
CREATE servers/gateway/tsconfig.json
CREATE servers/gateway/tsconfig.lib.json
CREATE servers/gateway/Dockerfile.dockerignore
CREATE servers/gateway/Dockerfile
CREATE servers/gateway/docker-compose.yml
CREATE servers/gateway/skills/create-tool/SKILL.md
…
```

A deployment shell: a `@FrontMcp` server that lists the apps (`Gateway Server`), the files its target needs, and a `project.json` named `server-<name>`, tagged `scope:servers`, with the targets `build` (with `target: "<target>"`), `dev`, `typecheck`, and `deploy`, which depends on `build`. `main.ts` imports each app from `../../../apps/<app>/src/<app>.app`.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | | The folder; the project is `server-<name>`. |
| `--apps` | `string` | | **Required.** Comma-separated app names to import and list in `apps`. |
| `--deploymentTarget` | `node`, `vercel`, `lambda`, `cloudflare` | `node` | `node` writes `Dockerfile`, `Dockerfile.dockerignore` and `docker-compose.yml`; `vercel` writes `vercel.json`; `lambda` writes `template.yaml` and adds `@codegenie/serverless-express` `^5.0.0` to the root `package.json`, which the generator's install puts in `node_modules` (since 1.9.3; before, you ran it); `cloudflare` writes `wrangler.toml`. |
| `--redis` | `docker`, `existing`, `none` | `none` | `node` only. `docker` adds a `redis:7-alpine` service to `docker-compose.yml`, published on `127.0.0.1:6379` only (since 1.9.3; before, on every interface), with `REDIS_HOST=redis` for the app. |
| `--skills` | `recommended`, `minimal`, `full`, `none` | `recommended` | Copies catalog skills into `servers/<name>/skills/`: 9 skills and 348 files by default, 4 and 193 for `minimal`, 13 and 376 for `full`. |
| `--directory` | `string` | `servers/<name>` | |
| `--tags` | `string` | `scope:servers` | |
| `--skipFormat` | `boolean` | `false` | |

`nx build server-gateway` builds each app first (`dependsOn: ["^build"]`), then the shell: one bundle, `servers/gateway/dist/node/server-gateway.bundle.js`, with the apps and any library they import compiled in. Before 1.8.7 the shell's imports didn't resolve, its `build` target passed an `--adapter` flag the CLI rejected, and the build stopped with `TS6059`.

The files written for the platform name what the build writes:

| `--deploymentTarget` | File | What it does |
| --- | --- | --- |
| `node` | `Dockerfile`, `Dockerfile.dockerignore` | Built from the workspace root (`docker build -f servers/gateway/Dockerfile .`): copies the workspace, installs with the workspace's package manager (`npm ci --ignore-scripts` in an npm workspace; pnpm, Yarn and bun get their own commands), runs `nx build server-gateway`, prunes the dev dependencies, and starts `node dist/node/server-gateway.bundle.js` as an unprivileged `appuser`, with `FRONTMCP_BIND_ADDRESS=all`, so a published port reaches it. Its `HEALTHCHECK` asks `/health` with Node's `fetch`, as in the Dockerfile of `frontmcp create` (since 1.9.3; before, it had none). Give the container `MCP_SESSION_SECRET`. Docker reads `Dockerfile.dockerignore` for this Dockerfile, which keeps `node_modules` and `dist` out of the context. |
| `node` | `docker-compose.yml` | Builds that image with the workspace root as context, publishes port `3000` (`PORT`), and sets `NODE_ENV` to `production` unless you set it. It requires `MCP_SESSION_SECRET`, from your shell or a `.env` file beside it, and without one stops with `required variable MCP_SESSION_SECRET is missing a value: set MCP_SESSION_SECRET (openssl rand -hex 32)`. Its comment says to generate the secret once and keep it, since a new value ends every open session. (In 1.9.2 it passed none.) |
| `vercel` | `vercel.json` | `installCommand` and `buildCommand` run from the workspace root: `cd ../../ && npm install` and `cd ../../ && npx nx build server-gateway` in an npm workspace. |
| `lambda` | `template.yaml` | `CodeUri: dist/lambda/` and `Handler: handler.handler`, the `handler.cjs` the build writes, with `NODE_ENV: production` and `MCP_SESSION_SECRET` from a required `NoEcho` parameter, `McpSessionSecret`, as in the template of `frontmcp create` ([AWS Lambda](https://frontmcp.dev/reference/deployment/aws-lambda#deploying-with-sam)). Its comment says the function needs no layer, since the build puts `@codegenie/serverless-express` in `handler.cjs`. (In 1.9.2 it set neither variable, and its comment asked for a layer.) |
| `cloudflare` | `wrangler.toml` | `main = "dist/cloudflare/index.js"`, `compatibility_date = "2024-11-11"` (since 1.9.2) and `compatibility_flags = ["nodejs_compat"]`, and `NODE_ENV = "production"` in `[vars]`. |

The `Dockerfile` copies the workspace before it installs, so the lock file has to list every project's `package.json`, the new shell's included. Since 1.9.3 the generator runs your package manager's install after it writes the shell (in an npm workspace it printed `added 1 package`), so the lock file lists it. The image, built straight after `nx g`, started as `appuser`, answered `/health` and MCP at `/` on the published port, `docker inspect` reported it `healthy`, and `docker stop` ended it at once with exit code `0`. In 1.9.2 the generator didn't install, and the image stopped at `npm ci` with `Missing: server-gateway@0.0.1 from lock file` until you did. Before 1.9.2 the `Dockerfile` assumed npm whatever the workspace used. In 1.8.7 it ran a `dist/main.js` nothing writes, `vercel.json` and `template.yaml` pointed at files the build doesn't write either, `.dockerignore` sat where Docker doesn't read it, and the shell had no `dev` target.

### Component generators

Each writes one file (two for `plugin --withContextExtension`) into `<project root>/src/<folder>/`, or `<project root>/skills/` for `skill-dir`. All thirteen outputs pass the app's `nx typecheck`, and an app that lists eleven of them in `@App` starts, with `jobs: { enabled: true }` on its server for the job and the workflow, and `OPENAI_API_KEY` set for the agent. `skill-dir` writes a folder, and no `@App` option takes a flow ([`flow`](#flow)). They write no test.

| Generator | Writes | Options besides the common ones |
| --- | --- | --- |
| [`tool`](#tool) | `src/tools/<name>.tool.ts` | |
| [`resource`](#resource) | `src/resources/<name>.resource.ts` | `--template` |
| [`prompt`](#prompt) | `src/prompts/<name>.prompt.ts` | `--arguments` |
| [`skill`](#skill) | `src/skills/<name>.skill.ts` | `--tools` |
| [`skill-dir`](#skill-dir) | `skills/<name>/SKILL.md` | `--description`, `--tags`, `--withReferences` |
| [`job`](#job) | `src/jobs/<name>.job.ts` | |
| [`workflow`](#workflow) | `src/workflows/<name>.workflow.ts` | |
| [`agent`](#agent) | `src/agents/<name>.agent.ts` | `--model`, `--tools` |
| [`provider`](#provider) | `src/providers/<name>.provider.ts` | `--scope` |
| [`plugin`](#plugin) | `src/plugins/<name>.plugin.ts` | `--withContextExtension` |
| [`adapter`](#adapter) | `src/adapters/<name>.adapter.ts` | |
| [`auth-provider`](#auth-provider) | `src/auth/<name>.auth-provider.ts` | `--type` |
| [`flow`](#flow) | `src/flows/<name>.flow.ts` | |

The last six wrote classes that didn't type-check before 1.8.7 (`agent`, `provider`, `plugin`, `adapter`, `auth-provider`, `flow`). Registering a class is still your job, and some need more than the list: an agent needs its key, a workflow needs your jobs.

#### `tool`

```ts apps/tickets/src/tools/search-tickets.tool.ts
import { Tool, ToolContext, ToolInputOf, ToolOutputOf, z } from '@frontmcp/sdk';

const inputSchema = {
  value: z.string().describe('TODO: replace with actual input'),
};

const outputSchema = {
  result: z.string().describe('TODO: replace with actual output'),
};

export type SearchTicketsInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
export type SearchTicketsOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;

@Tool({
  name: 'search_tickets',
  description: 'TODO: describe what this tool does',
  inputSchema,
  outputSchema,
})
export default class SearchTicketsTool extends ToolContext {
  async execute(input: SearchTicketsInput): Promise<SearchTicketsOutput> {
    // TODO: implement
    return { result: input.value };
  }
}
```

(Trimmed: the file also has a comment about hoisting the schemas.) It's a default export. [`@Tool`](https://frontmcp.dev/reference/sdk/tool) has every option; [Usage](#adding-a-tool-to-an-app) registers one.

#### `resource`

Without `--template`: a [`@Resource`](https://frontmcp.dev/reference/sdk/resource) at `<name>://data`, returning `{}` as JSON. With `--template`: a [`@ResourceTemplate`](https://frontmcp.dev/reference/sdk/resource-template) at `<name>://{id}` that returns `{ id }`. Both are default exports.

#### `prompt`

A [`@Prompt`](https://frontmcp.dev/reference/sdk/prompt) returning one user message with `TODO` text. `--arguments ticketId,tone` declares each argument, all with `required: true`. The file imports its return type from `@modelcontextprotocol/sdk/types.js`, a package `@frontmcp/sdk` depends on. It resolves when your package manager hoists dependencies, as npm does; otherwise install it, or drop the annotation.

#### `skill`

A [`@Skill`](https://frontmcp.dev/reference/sdk/skill) with `TODO` instructions and an empty class body. `--tools search_tickets,close_ticket` fills `tools`; use the tools' listed names.

#### `skill-dir`

```markdown apps/tickets/skills/escalation/SKILL.md
---
name: escalation
description: How to escalate a ticket
tags: [support, escalation]
---

# escalation

Add your skill instructions here.
```

(Trimmed: it ends with a `Steps` list of three placeholders.) `--withReferences` adds a `references/` folder with only a `.gitkeep` in it, `--directory` another folder under the project. The server doesn't find the folder on its own: load it with [`skillDir()`](https://frontmcp.dev/reference/sdk/skill#skilldirdir) and list the result in an app's `skills`. `skillDir()` reads `SKILL.md` and the `references/` folder too: each file you put there is listed in a table under the skill's instructions and served at `skill://escalation/references/<file>`. The `.gitkeep` isn't listed.

#### `job`

A [`@Job`](https://frontmcp.dev/reference/sdk/job) with a `value` input and a `result` output, returning `Processed: <value>`. Registered in an app with jobs on, it adds `execute_job` and `get_job_status` to the tools.

#### `workflow`

A [`@Workflow`](https://frontmcp.dev/reference/sdk/workflow) with `trigger: 'manual'` and two steps, `step1` and `step2`, that run the jobs `TODO-first-job` and `TODO-second-job`, the second reading the first's `result`. The server starts with it registered, and adds `execute_workflow` and `get_workflow_status` to the tools; point the steps at your own jobs before running it.

#### `agent`

```ts
@Agent({
  name: 'desk-agent',
  description: 'TODO: describe what this agent does',
  llm: { provider: 'openai', model: 'gpt-4', apiKey: { env: 'OPENAI_API_KEY' } },
  inputSchema: { task: z.string().describe('TODO: describe the task the agent should perform') },
  systemInstructions: 'TODO: define system instructions for this agent',
})
```

`--model` (default `gpt-4`) fills `llm.model`, and `--tools search_tickets` imports that tool's class and adds `tools: [SearchTicketsTool]`. The `execute()` calls `super.execute(input)`, which runs the model's loop. A server that lists the agent needs `OPENAI_API_KEY`: without it, startup fails with `Failed to create LLM adapter for agent desk-agent` and `Environment variable OPENAI_API_KEY is not set`. See [`@Agent`](https://frontmcp.dev/reference/sdk/agent).

#### `provider`

`--scope` is `singleton` (the default, written as `ProviderScope.GLOBAL`), `request` or `context` (both `ProviderScope.CONTEXT`). The class is `@Provider({ name: '<Name>Provider', scope })`, with a comment on registering it in `providers` and resolving it with `this.get(<Name>Provider)`. See [`@Provider`](https://frontmcp.dev/reference/sdk/provider).

#### `plugin`

A `DynamicPlugin` subclass with options and a `static dynamicProviders()` that returns an empty list. `--withContextExtension` adds `<name>.context-extension.ts`, which declares `this.<name>` on every context and exports `install<Name>ContextExtension()`, which defines it on `ExecutionContextBase.prototype`. The generated plugin doesn't call it: call it once at startup. See [`@Plugin`](https://frontmcp.dev/reference/sdk/plugin).

#### `adapter`

A `DynamicAdapter` subclass whose constructor takes `{ name } & Options` and whose `fetch()` returns empty `tools`, `resources` and `prompts`. List it in an app as `adapters: [CrmAdapter.init({ name: 'crm' })]`. See [`@Adapter`](https://frontmcp.dev/reference/sdk/adapter).

#### `auth-provider`

`--type` is `bearer` (the default), `oauth`, `api-key`, `basic` or `custom`, and decides the `headers()` method: `bearer` reads `<NAME>_TOKEN`, `api-key` `<NAME>_API_KEY` into `X-API-Key`, `basic` `<NAME>_USERNAME` and `<NAME>_PASSWORD`, from the environment (`desk-bearer` reads `DESK_BEARER_TOKEN`); `oauth` and `custom` are `TODO`s. List it in an app's `authProviders`. For how tools get credentials, see [Progressive auth](https://frontmcp.dev/reference/auth/progressive#credentials-authproviders-and-thiscredentials).

#### `flow`

A `FlowBase` subclass with `pre`, `execute`, `post` and `finalize` stages. It declares its name on `ExtendFlows` and its `plan` as an object of stages, the shape [Flows and stages](https://frontmcp.dev/reference/server/flows) describes, so it type-checks as written. No `@App` option takes it (`flows` there is `TS2353`): register it at run time with `scope.registryFlows()`, as [Flows and stages](https://frontmcp.dev/reference/server/flows#registering-and-running) shows.

### UI generators

They take no `--project`: they write to `ui/` at the workspace root, and add a path alias to `tsconfig.base.json`. The first one you run for a folder creates its Nx project (`ui-components`, `ui-pages` or `ui-shells`, with `project.json`, `tsconfig.json`, `jest.config.cjs` and a barrel `src/index.ts`), adds what the generated files import to `package.json`, and installs it.

| Generator | Name | Writes | Alias added to `tsconfig.base.json` |
| --- | --- | --- | --- |
| `ui-component` | PascalCase, like `TicketCard` | `ui/components/src/TicketCard/TicketCard.tsx`, `TicketCard.spec.tsx`, `index.ts` | `@frontmcp/ui-components/TicketCard` |
| `ui-page` | PascalCase, like `AgentDashboard` | `ui/pages/src/AgentDashboard/AgentDashboard.tsx`, `.spec.tsx`, `index.ts` | `@frontmcp/ui-pages/AgentDashboard` |
| `ui-shell` | kebab-case, like `help-desk-shell` | `ui/shells/src/help-desk-shell/help-desk-shell.shell.ts`, `.shell.spec.ts`, `index.ts` | `@frontmcp/ui-shells/help-desk-shell` |

Each also takes `--description` (default empty) and `--skipFormat`.

- `ui-component` and `ui-page` write React components built with `@mui/material` (`<Box>`, `<Typography>`), and add `react`, `react-dom`, `@mui/material`, `@emotion/react`, `@emotion/styled`, their types, `@testing-library/react` and `@testing-library/dom`, and `jest-environment-jsdom`.
- `ui-shell` writes `build<Name>Shell({ toolName, input?, output? })`, which wraps a `<div>` in `buildShell()` from `@frontmcp/uipack` and returns `{ html, hash, size }`. It adds `@frontmcp/uipack` and `esbuild` `^0.27.3` (and `@nx/esbuild` at the workspace's Nx version when the workspace has none: 23.2.0 in a `--preset=ts` workspace on Nx 23.2.0).
- All three install, their packages type-check with `tsc -p ui/<folder>/tsconfig.json --noEmit`, and the three tests each one writes pass. In 1.8.7 `ui-shell` added `esbuild` `^0.25.0`, below what `@frontmcp/uipack` accepts, and the install stopped with `ERESOLVE`.
- The packages get `build-cjs` and `build-esm` targets (`@nx/esbuild:esbuild`) and a `build` that runs both: `nx build ui-components` bundles to `dist/ui/components/`. `build-cjs` type-checks; `build-esm` doesn't, so a TypeScript-solution `tsconfig.base.json` doesn't stop it. A `test` target comes from `jest.config.cjs` when `nx.json` has `@nx/jest/plugin`, as in a `frontmcp create --nx` workspace; otherwise the generator writes one into `project.json` and installs Jest and `@swc/jest`.

In a `create-nx-workspace --preset=ts` workspace, all three build and pass their tests too: `nx run-many -t build test -p ui-components`, `ui-pages` and `ui-shells` succeeded there, each with a `test` target of its own. Before 1.9.2, `build-esm` stopped there with `error TS5069: Option 'declarationMap' cannot be specified without specifying option 'declaration' or option 'composite'.`, and the packages had no `test` target.

#### Caveats

- Every generated `project.json` points `$schema` at `../…/node_modules/…`, and every `tsconfig.json` extends `../…/tsconfig.base.json`, with as many `../` as the project is deep.
- The generators never edit a project's existing files, only add new ones. The ones that add dependencies, aliases or targets edit the workspace's `package.json`, `tsconfig.base.json` or `nx.json`: `init`, `lib`, `server --deploymentTarget lambda` and the UI generators.
- The aliases `lib` and the UI generators add are written with a leading `./` (`./libs/<name>/src/index.ts`), which works whether or not `tsconfig.base.json` has a `baseUrl`. Aliases written by 1.8.7 have no `./`, and TypeScript refuses them (`TS5090`) in a workspace without a `baseUrl`, like the one `create-nx-workspace --preset=ts` makes.

---

## Usage

### Adding a tool to an app

```bash
nx g @frontmcp/nx:tool search_tickets --project tickets
```

The generator writes the file; you import it into the app and list it. Here's the result, with the generated file unchanged:

```ts tickets.app.ts active
import { App } from "@frontmcp/sdk";
import SearchTicketsTool from "./search-tickets.tool";

@App({
  id: "tickets",
  name: "Tickets",
  tools: [SearchTicketsTool], // ✅ the generator doesn't add it
})
export class TicketsApp {}
```

```ts search-tickets.tool.ts
import { Tool, ToolContext, ToolInputOf, ToolOutputOf, z } from "@frontmcp/sdk";

// Hoist only the schemas — the rest of the @Tool config (name, description,
// annotations, throttling, …) stays inside @Tool({…}) where it naturally
// lives. Changing a schema field automatically updates the derived TS types.
const inputSchema = {
  value: z.string().describe("TODO: replace with actual input"),
};

const outputSchema = {
  result: z.string().describe("TODO: replace with actual output"),
};

export type SearchTicketsInput = ToolInputOf<{ inputSchema: typeof inputSchema }>;
export type SearchTicketsOutput = ToolOutputOf<{ outputSchema: typeof outputSchema }>;

@Tool({
  name: "search_tickets",
  description: "TODO: describe what this tool does",
  inputSchema,
  outputSchema,
})
export default class SearchTicketsTool extends ToolContext {
  async execute(input: SearchTicketsInput): Promise<SearchTicketsOutput> {
    // TODO: implement
    return { result: input.value };
  }
}
```

```ts main.ts
import { FrontMcp } from "@frontmcp/sdk";
import { TicketsApp } from "./tickets.app";

@FrontMcp({
  info: { name: "Tickets", version: "0.1.0" },
  apps: [TicketsApp],
})
export default class Server {}
```

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

test("the generated tool is listed once the app lists it", async ({ mcp }) => {
  expect(await mcp.tools.list()).toContainTool("search_tickets");
});

test("it echoes its input until you implement it", async ({ mcp }) => {
  const result = await mcp.tools.call("search_tickets", { value: "log in" });
  expect(result).toBeSuccessful();
  expect(result.json()).toEqual({ result: "log in" });
});
```

Replace the schemas and `execute()` next, and the `TODO` description, which is what the model reads.

### Adding a resource template

```bash
nx g @frontmcp/nx:resource ticket --project tickets --template
```

```ts tickets.app.ts active
import { App } from "@frontmcp/sdk";
import TicketResource from "./ticket.resource";

@App({ id: "tickets", name: "Tickets", resources: [TicketResource] })
export class TicketsApp {}
```

```ts ticket.resource.ts
import { ResourceTemplate, ResourceContext } from "@frontmcp/sdk";

@ResourceTemplate({
  name: "ticket",
  uriTemplate: "ticket://{id}",
  description: "TODO: describe this resource template",
  mimeType: "application/json",
})
export default class TicketResource extends ResourceContext {
  async execute(uri: string, params: Record<string, string>) {
    // TODO: implement — params.id available from URI template
    return {
      contents: [
        {
          uri,
          mimeType: "application/json",
          text: JSON.stringify({ id: params.id }),
        },
      ],
    };
  }
}
```

```ts main.ts
import { FrontMcp } from "@frontmcp/sdk";
import { TicketsApp } from "./tickets.app";

@FrontMcp({ info: { name: "Tickets", version: "0.1.0" }, apps: [TicketsApp] })
export default class Server {}
```

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

test("the template is listed and fills in the id", async ({ mcp }) => {
  expect(await mcp.resources.listTemplates()).toContainResourceTemplate("ticket://{id}");
  const content = await mcp.resources.read("ticket://T-1");
  expect(content.json()).toEqual({ id: "T-1" });
});
```

### Scaffolding a server

```bash
nx g @frontmcp/nx:server gateway --apps demo,tickets --skills none
```

The shell is ready to build, with the imports pointing at the apps' `src/` folders:

```ts servers/gateway/src/main.ts
import 'reflect-metadata';
import { FrontMcp } from '@frontmcp/sdk';
import { DemoApp } from '../../../apps/demo/src/demo.app';
import { TicketsApp } from '../../../apps/tickets/src/tickets.app';

@FrontMcp({
  info: { name: 'Gateway Server', version: '0.1.0' },
  apps: [DemoApp, TicketsApp],
})
export default class Server {}
```

```bash
nx build server-gateway
```

```text
 NX   Running target build for project server-gateway and 2 tasks it depends on:
Running: frontmcp build --entry …/apps/demo/src/main.ts --out-dir …/apps/demo/dist (in …/apps/demo)
Running: frontmcp build --entry …/apps/tickets/src/main.ts --out-dir …/apps/tickets/dist (in …/apps/tickets)
Running: frontmcp build --target node --entry …/servers/gateway/src/main.ts --out-dir …/servers/gateway/dist (in …/servers/gateway)
[build:exec] bundle created: dist/node/server-gateway.bundle.js (26.8 KB)

 NX   Successfully ran target build for project server-gateway and 2 tasks it depends on
```

`nx dev server-gateway` serves both apps while you work, and `nx typecheck server-gateway` checks the shell with the apps it imports. To ship it as a container, build the image from the workspace root; the generator's install has already added the shell to the lock file:

```bash
docker build -f servers/gateway/Dockerfile -t help-desk-gateway .
docker run -p 3000:3000 -e MCP_SESSION_SECRET=... help-desk-gateway
```

[Monorepo patterns](https://frontmcp.dev/reference/nx/monorepo#composing-apps-into-one-server) covers composing apps.

### Previewing what a generator writes

```bash
nx g @frontmcp/nx:app billing --directory apps/team/billing --tags scope:billing,type:app --dry-run
```

```text
 NX  Generating @frontmcp/nx:app

CREATE apps/team/billing/jest.config.cjs
CREATE apps/team/billing/package.json
CREATE apps/team/billing/project.json
CREATE apps/team/billing/src/billing.app.ts
…
NOTE: The "dryRun" flag means no changes were made.
```

---

## Troubleshooting

### `The workspace is probably out of sync … Missing root "tsconfig.json"`

`nx.json` still lists `@nx/js/typescript`, as a workspace made with 1.8.7 does, and it gives a library made by `lib` an inferred `build` that needs a root `tsconfig.json`. See [Upgrading a 1.8.7 workspace](https://frontmcp.dev/reference/nx#upgrading-a-187-workspace).

### The new tool (or resource, prompt, job, …) isn't listed

Generators don't register classes. Import it into the app file and add it to `tools`, `resources`, `prompts`, `jobs` and so on in `@App`.

### `Required property 'project' is missing`

Component generators need `--project <name>`: the Nx project name, like `tickets` or `server-gateway`, as `nx show projects` lists them.

### `has no exported member 'PluginRegistrationContext'` or `'AdapterFetchResult'`

A library made by the 1.8.7 `nx g @frontmcp/nx:lib … --libType plugin` or `--libType adapter`. Replace the class with what the 1.9.2 generator writes, the same shape as the component generators' ([`plugin`](#plugin), [`adapter`](#adapter)).

### `Cannot find configuration for task server-gateway:dev`

A server shell made before 1.9, when the `server` generator wrote only `build` and `deploy`. Add the target a 1.9.3 shell has: `"dev": { "executor": "@frontmcp/nx:dev", "options": { "entry": "{projectRoot}/src/main.ts" } }`.

### `Failed to create LLM adapter for agent … Environment variable OPENAI_API_KEY is not set`

A server lists an agent from the [`agent`](#agent) generator, which reads its key from `OPENAI_API_KEY`. Set the variable, or change `llm` in the file.

### `error TS5069: Option 'declarationMap' cannot be specified …` from a UI package's build

A UI package generated before 1.9.2 in a `create-nx-workspace --preset=ts` workspace. Add `"composite": false, "declarationMap": false, "emitDeclarationOnly": false` to the `compilerOptions` of `ui/<folder>/tsconfig.json`, or compare its `project.json` with one the 1.9.2 generator writes ([UI generators](#ui-generators)).

### `The import path "…" is already mapped in tsconfig.base.json`

`lib` refuses an alias another library has. Pass `--importPath` with another name. The same goes for `… is a package this workspace depends on`, an alias like `@frontmcp/sdk` that would shadow a dependency.

### `error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set`

An alias written by the 1.8.7 `lib` or UI generators, in a `tsconfig.base.json` that has no `baseUrl`. Write the target as `./libs/<name>/src/index.ts`, as 1.9.2 does.
