# Nx executors

> Every executor in @frontmcp/nx (build, build-exec, dev, serve, test, inspector and deploy): the options each takes, the command it runs, what is cached, and how to run an app's tests and build it for another target.

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

The executors of `@frontmcp/nx` let Nx run the [`frontmcp` CLI](https://frontmcp.dev/reference/cli): `nx build tickets` runs `frontmcp build` with the project's entry and output folder, and Nx adds caching, `dependsOn` ordering and `nx affected` on top. Each one turns its options into CLI flags and runs the workspace's own `frontmcp` from **the project's folder**, so the CLI reads the project's `package.json`, `tsconfig.json`, `.env` and `frontmcp.config.*`. `deploy` is the exception: it runs a platform's own CLI in the project's folder. Before 1.8.7 they ran `npx frontmcp` at the workspace root, and most of their surprises came from that.

```json apps/tickets/project.json
{
  "targets": {
    "build": {
      "executor": "@frontmcp/nx:build",
      "cache": true,
      "outputs": ["{projectRoot}/dist"],
      "options": { "entry": "{projectRoot}/src/main.ts", "outputPath": "{projectRoot}/dist" }
    },
    "dev": { "executor": "@frontmcp/nx:dev", "options": { "entry": "{projectRoot}/src/main.ts" } }
  }
}
```

---

## Reference

### How an executor runs

| Executor | Runs | From | Kind |
| --- | --- | --- | --- |
| [`build`](#build) | `frontmcp build [--target] --entry <path> --out-dir <path>` | Project folder | Runs to the end |
| [`build-exec`](#build-exec) | `frontmcp build --target node --entry <path> --out-dir <path>` | Project folder | Runs to the end |
| [`dev`](#dev) | `frontmcp dev --entry <path> [--port]` | Project folder | Long-running |
| [`serve`](#serve) | `frontmcp start <project> [--entry] [--port] [--max-restarts]` | Project folder | Long-running |
| [`test`](#test) | `frontmcp test [--runInBand] [--watch] [--coverage] [--verbose] [--timeout]` | Project folder | Runs to the end |
| [`inspector`](#inspector) | `frontmcp inspector`, with `CLIENT_PORT` set from `port` | Project folder | Long-running |
| [`deploy`](#deploy) | The target platform's CLI | Project folder | Runs to the end |

Each prints the command first, with the folder it runs in (`Running: frontmcp build --entry /Users/you/help-desk-platform/apps/tickets/src/main.ts --out-dir /Users/you/help-desk-platform/apps/tickets/dist (in /Users/you/help-desk-platform/apps/tickets)`), runs it with the terminal's output and `FORCE_COLOR=1`, and succeeds when it exits with `0`. Paths in options are made absolute from the workspace root first. A long-running one ends when its command does, on Ctrl+C or when the server exits. Options can be set in `project.json` or on the command line (`nx dev tickets --port=4100`), and `{projectRoot}` in them is replaced by the project's folder.

The CLI is the one in the workspace's `node_modules/frontmcp`. If it isn't there, the target stops with `The "frontmcp" CLI is not installed in …`; the executors never fall back to `npx`, which would download the newest version ([Nx plugin](https://frontmcp.dev/reference/nx#installing-it)).

Because the CLI runs in the project's folder, a bundle is named after the project, with its own version: `tickets.bundle.js` and `0.0.1` from `apps/tickets/package.json`. A `frontmcp.config.ts` in the app's folder is read: its `name` and `version` name the bundle (`billing-desk.bundle.js`), and `transport.http` sets the port and path `nx dev` uses. [Configuration files](https://frontmcp.dev/reference/server/config-files#frontmcpconfig) lists the fields.

`build`, `build-exec` and `test` are cacheable. The `app` generator writes `cache: true` on its `build` and `test` targets (and on `typecheck`, which runs `tsc`, not an executor), and `init` (run by `nx add @frontmcp/nx`) adds `targetDefaults` that cache all three by executor: `build` and `build-exec` also `dependsOn` `^build`, so Nx builds the projects an app imports first. `test` keeps Nx's default inputs, every file of the project, so editing a test runs the tests again. `dev`, `serve`, `inspector` and `deploy` aren't cached. A cache hit replays the recorded terminal output, including its absolute paths, and restores the target's `outputs`.

### `build`

| Option | CLI flag | Description |
| --- | --- | --- |
| `entry` | `--entry` | The server's entry file, relative to the workspace root. |
| `outputPath` | `--out-dir` | The base output folder. The build writes to `<outputPath>/<target>/`, so `apps/tickets/dist/node/` by default. |
| `target` | `--target` | `node` (the default), `vercel`, `lambda`, `cloudflare`, `distributed`, `cli`, `sdk`, `browser` or `mcpb`. |
| `adapter` | `--target` | The old spelling of `target`, kept as an alias: `node`, `vercel`, `lambda` or `cloudflare`. Use `target`. |

Every target builds from a project made by the `app` generator: `node`, `sdk`, `browser`, `vercel`, `cloudflare`, `distributed`, `mcpb` and `cli`. `lambda` stops with `missing required peer dependency @codegenie/serverless-express` until that package is in the workspace; `nx g @frontmcp/nx:server … --deploymentTarget lambda` adds it to `package.json`. Files a target writes next to the project, `vercel.json` and `wrangler.toml`, go in the project's folder, with paths relative to it. [The CLI page](https://frontmcp.dev/reference/cli#the-targets) says what each target writes.

`nx build tickets --target=vercel` doesn't work: Nx reads `--target` itself and stops with `Cannot find configuration for task tickets:build,vercel`. Set `target` in `project.json`, in a configuration, or use the `nx run` form, which passes it on: [Building for another target](#building-for-another-target).

### `build-exec`

| Option | CLI flag | Description |
| --- | --- | --- |
| `entry` | `--entry` | |
| `outputPath` | `--out-dir` | Writes to `<outputPath>/node/`. |

The same as `build` with `--target node` spelled out. No generator adds it to a project: add a target with `"executor": "@frontmcp/nx:build-exec"` to use it. It's cached like `build`.

### `dev`

| Option | CLI flag | Description |
| --- | --- | --- |
| `entry` | `--entry` | |
| `port` | `--port` | The port. Default: [`frontmcp dev`'s](https://frontmcp.dev/reference/cli#frontmcp-dev), which is `transport.http.port` from the app's `frontmcp.config`, then `PORT`, then `3000`. |

```text
$ npx nx dev tickets --port=4110
> nx run tickets:dev --port=4110
Running: frontmcp dev --entry /Users/you/help-desk-platform/apps/tickets/src/main.ts --port 4110 (in /Users/you/help-desk-platform/apps/tickets)
[dev] using entry: src/main.ts
[dev] listening on port: 4110
…
MCP HTTP (Express) on 127.0.0.1:4110
[10:01:12 AM] Found 0 errors. Watching for file changes.
```

The type checker `frontmcp dev` starts runs in the project's folder too, with the app's `tsconfig.json`, so it checks the app.

### `serve`

| Option | CLI flag | Default | Description |
| --- | --- | --- | --- |
| `entry` | `--entry` | | |
| `port` | `--port` | | |
| `maxRestarts` | `--max-restarts` | `5` | How many times the supervisor restarts a server that exits. |

Runs the [process manager's](https://frontmcp.dev/reference/cli#process-manager) `frontmcp start`, with the Nx project's name as the process name, so `frontmcp list`, `logs tickets` and `stop tickets` work from any terminal. It stays in the foreground, like `start`.

```text
$ npx nx serve tickets --port=4105
Running: frontmcp start tickets --entry /Users/you/help-desk-platform/apps/tickets/src/main.ts --port 4105 --max-restarts 5 (in /Users/you/help-desk-platform/apps/tickets)
[pm] starting "tickets"...
[pm] entry: src/main.ts

Started successfully:

Name:        tickets
Status:      running
Port:        4105
```

### `test`

| Option | CLI flag | Default |
| --- | --- | --- |
| `runInBand` | `--runInBand` | `false` |
| `watch` | `--watch` | `false` |
| `coverage` | `--coverage` | `false` |
| `verbose` | `--verbose` | `false` |
| `timeout` | `--timeout <ms>` | |

Runs `frontmcp test` in the project's folder, where it finds the `jest.config.cjs` the generator wrote and runs it. That configuration compiles with `@swc/jest`, loads `@frontmcp/testing/setup` for the matchers, and maps the aliases in `tsconfig.base.json`'s `paths` to their folders, so a test can import a library through its alias. Every `*.spec.ts` and `*.test.ts` in the project is a test.

A new app comes with one, `src/tools/hello.tool.spec.ts`, and a library with one too, so `nx test` and `nx run-many -t test` pass as generated:

```text
> nx run demo:test
Running: frontmcp test (in /Users/you/help-desk-platform/apps/demo)
[test] running tests in .
[test] using user Jest config: jest.config.cjs
hint: press Ctrl+C to stop
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
…
 NX   Successfully ran target test for project demo
```

In 1.8.7 a new app had no test, and `nx test` failed with `No tests found, exiting with code 1`. A library's `test` target is `@frontmcp/nx:test` too since 1.9; it was `@nx/jest:jest`.

### `inspector`

| Option | CLI flag | Description |
| --- | --- | --- |
| `port` | `CLIENT_PORT` | The port of the Inspector's page. The CLI has no port flag, so the executor sets this variable, which the Inspector reads. |

Runs [`frontmcp inspector`](https://frontmcp.dev/reference/cli#frontmcp-inspector), which starts the Inspector at `http://localhost:6274`, or at `port` when you set it: `nx inspector tickets --port=4400` opens it at `http://127.0.0.1:4400`. The `Running:` line shows `frontmcp inspector` without a flag. An app without a `frontmcp.config` gives the Inspector no server address: enter `http://localhost:3000/` (or the `dev` port) yourself. With a config in the app's folder that has `transport.http`, the Inspector is pointed at it.

### `deploy`

| Option | Type | Description |
| --- | --- | --- |
| `target` | `node`, `vercel`, `lambda`, `cloudflare` | **Required.** Which command to run. |

| `target` | Command, run in the project's folder |
| --- | --- |
| `node` | `docker compose up --build -d` |
| `vercel` | `npx vercel deploy --prebuilt --prod`: it uploads the `.vercel/output/` that `build` wrote, instead of building again on Vercel (since 1.9.2; before, `npx vercel --prod`) |
| `lambda` | `sam build && sam deploy` |
| `cloudflare` | `npx wrangler deploy` |

It runs nothing else: those tools, and your login to the platform, have to be set up. For `node`, the shell's `docker-compose.yml` requires `MCP_SESSION_SECRET` since 1.9.3, from your shell or a `.env` file in the shell's folder; without it the target fails with `required variable MCP_SESSION_SECRET is missing a value`. The `server` generator's `deploy` target has `dependsOn: ["build"]`, so it builds first, and since 1.9 the files it wrote for the platform name what the build writes ([Generators](https://frontmcp.dev/reference/nx/generators#server)).

```text
$ npx nx run server-gw-lambda:deploy --exclude-task-dependencies
Deploying server-gw-lambda to lambda...
Running: sam build && sam deploy
/bin/sh: sam: command not found
```

#### Caveats

- `dev`, `serve` and `inspector` run until you stop them.
- `outputs` in the generated `build` target is `{projectRoot}/dist`, which holds `dist/node/` and every other target you build, so the cache restores them.

---

## Usage

### Building an app

```bash
npx nx build tickets
```

```text
> nx run tickets:build

Running: frontmcp build --entry /Users/you/help-desk-platform/apps/tickets/src/main.ts --out-dir /Users/you/help-desk-platform/apps/tickets/dist (in /Users/you/help-desk-platform/apps/tickets)
[build] cleaned dist/node
[build:exec] Building executable bundle...
[build:exec] name: tickets
[build:exec] version: 0.0.1
[build:exec] entry: src/main.ts
…
[build:exec] bundle created: dist/node/tickets.bundle.js (24.3 KB)

 NX   Successfully ran target build for project tickets
```

Run it again without changes, and Nx replays it from the cache:

```text
> nx run tickets:build  [local cache]

 NX   Successfully ran target build for project tickets

Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
```

Run the result as any [`node` build](https://frontmcp.dev/reference/cli#the-node-target): `PORT=8080 apps/tickets/dist/node/tickets`, from the workspace, where the packages are. An app can import code from outside its own folder, a library through its alias or a shell's apps by relative path, and the build bundles it ([Monorepo patterns](https://frontmcp.dev/reference/nx/monorepo)).

### Building for another target

`target` picks the target, but not on the command line through the `nx build` shorthand. Three ways that work:

```json apps/tickets/project.json
{
  "targets": {
    "build": {
      "executor": "@frontmcp/nx:build",
      "cache": true,
      "outputs": ["{projectRoot}/dist"],
      "options": { "entry": "{projectRoot}/src/main.ts", "outputPath": "{projectRoot}/dist" },
      "configurations": { "vercel": { "target": "vercel" }, "cloudflare": { "target": "cloudflare" } }
    },
    "build-distributed": {
      "executor": "@frontmcp/nx:build",
      "cache": true,
      "outputs": ["{projectRoot}/dist/distributed"],
      "options": { "entry": "{projectRoot}/src/main.ts", "outputPath": "{projectRoot}/dist", "target": "distributed" }
    }
  }
}
```

```bash
npx nx build tickets -c vercel                 # a configuration
npx nx run tickets:build-distributed           # a target of its own, cached like build
npx nx run tickets:build --target=cloudflare   # the nx run form passes --target on
```

Each builds into `apps/tickets/dist/<target>/`, and the cache keeps them apart. `nx build tickets --target=vercel` is read by Nx and fails.

### Running an app while you work

```bash
npx nx dev tickets
npx nx dev demo --port=4101    # a second app, beside it
```

Each app is its own server, on its own port. Both answer at the root of their port, `http://localhost:3000/` and `http://localhost:4101/`, unless the app has a `frontmcp.config.ts` that sets `transport.http.path`.

### Running an app's tests

The generated `hello.tool.spec.ts` tests the tool's `execute()` alone. Add an end-to-end test beside it, and `nx test` runs both. A test in the app starts the app's server from the app's folder, since that's where the CLI runs:

```ts apps/tickets/e2e/tickets.e2e.spec.ts
import { expect, test } from "@frontmcp/testing";

test.use({ server: "./src/main.ts", port: 3110 });

test("hello greets by name", async ({ mcp }) => {
  const result = await mcp.tools.call("hello", { name: "Ada" });
  expect(result).toBeSuccessful();
  expect(result.json()).toEqual({ message: "Hello, Ada!" });
});
```

```text
$ npx nx test tickets
> nx run tickets:test

Running: frontmcp test (in /Users/you/help-desk-platform/apps/tickets)
[test] running tests in .
[test] using user Jest config: jest.config.cjs
hint: press Ctrl+C to stop
Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
…

 NX   Successfully ran target test for project tickets
```

A second run with nothing changed is replayed from the cache; after an edit to a test, they run again. Give each app's tests their own `port`, so `nx run-many -t test` doesn't start two servers on one. A plain unit spec, with no server, runs the same way, and can import a library through its alias:

```ts apps/tickets/src/ids.spec.ts
import { formatTicketId } from "@help-desk/ticket-model";

test("formats ticket ids through the alias", () => {
  expect(formatTicketId(7)).toBe("T-0007");
});
```

### Keeping an app running

```bash
npx nx serve tickets --port=4105     # stays in the foreground
npx frontmcp list                    # from another terminal
npx frontmcp stop tickets
```

`maxRestarts` in `project.json` sets how often the supervisor restarts it. The process manager keeps its files in `~/.frontmcp/`, shared by every project on the machine, so give apps in different workspaces different names.

---

## Troubleshooting

### `The "frontmcp" CLI is not installed in …`

The workspace has no `node_modules/frontmcp`. Run `npx nx g @frontmcp/nx:init`, or `npm install --save-dev frontmcp`.

### `Cannot find configuration for task tickets:build,vercel`

`nx build tickets --target=vercel`: Nx took `--target` for its own. Use `nx run tickets:build --target=vercel`, a configuration, or a target of its own ([Building for another target](#building-for-another-target)).

### `No tests found, exiting with code 1` from `nx test`

The project has no `*.spec.ts` or `*.test.ts`: an app made before 1.9, which came without one, or one whose generated spec you deleted. Add a test ([Running an app's tests](#running-an-apps-tests)).

### `Could not resolve "…/dist/node/main.js"`

`tsc` emitted no JavaScript: the app's `tsconfig.json` inherits `emitDeclarationOnly` from a TypeScript-solution `tsconfig.base.json`. An app generated with 1.9.2 sets it to `false`; for one made before, see [Nx plugin](https://frontmcp.dev/reference/nx#adding-the-plugin-to-a-workspace-you-have).

### `sam: command not found` (or `wrangler`, `vercel`, `docker`)

`deploy` runs the platform's tool and doesn't install it.

### `[--target lambda] missing required peer dependency @codegenie/serverless-express`

`nx build` with `target: "lambda"` needs that package in the workspace: `npm install @codegenie/serverless-express`.
