Nx executors

The executors of @frontmcp/nx let Nx run the frontmcp 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.

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

ExecutorRunsFromKind
buildfrontmcp build [--target] --entry <path> --out-dir <path>Project folderRuns to the end
build-execfrontmcp build --target node --entry <path> --out-dir <path>Project folderRuns to the end
devfrontmcp dev --entry <path> [--port]Project folderLong-running
servefrontmcp start <project> [--entry] [--port] [--max-restarts]Project folderLong-running
testfrontmcp test [--runInBand] [--watch] [--coverage] [--verbose] [--timeout]Project folderRuns to the end
inspectorfrontmcp inspector, with CLIENT_PORT set from portProject folderLong-running
deployThe target platform's CLIProject folderRuns 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).

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 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

OptionCLI flagDescription
entry--entryThe server's entry file, relative to the workspace root.
outputPath--out-dirThe base output folder. The build writes to <outputPath>/<target>/, so apps/tickets/dist/node/ by default.
target--targetnode (the default), vercel, lambda, cloudflare, distributed, cli, sdk, browser or mcpb.
adapter--targetThe 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 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.

build-exec

OptionCLI flagDescription
entry--entry
outputPath--out-dirWrites 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

OptionCLI flagDescription
entry--entry
port--portThe port. Default: frontmcp dev's, which is transport.http.port from the app's frontmcp.config, then PORT, then 3000.
$ 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

OptionCLI flagDefaultDescription
entry--entry
port--port
maxRestarts--max-restarts5How many times the supervisor restarts a server that exits.

Runs the process manager's 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.

$ 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

OptionCLI flagDefault
runInBand--runInBandfalse
watch--watchfalse
coverage--coveragefalse
verbose--verbosefalse
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:

> 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

OptionCLI flagDescription
portCLIENT_PORTThe 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, 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

OptionTypeDescription
targetnode, vercel, lambda, cloudflareRequired. Which command to run.
targetCommand, run in the project's folder
nodedocker compose up --build -d
vercelnpx 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)
lambdasam build && sam deploy
cloudflarenpx 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).

$ 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

npx nx build tickets
> 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:

> 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: 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).

Building for another target

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

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" }
    }
  }
}
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

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:

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!" });
});
$ 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:

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

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).

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).

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.

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.