Production build

frontmcp build turns your server into what a platform runs: a bundle and a start script for a Node process, a request handler for Vercel or AWS Lambda, a Worker for Cloudflare, a library, a browser module, a command-line program, or an MCP Bundle for desktop clients. Every target starts from the same @FrontMcp entry file, but each one bundles different things, serves its endpoint at a path chosen in a different way, and decides differently whether it runs in production. This page is the map; the pages after it deploy each target. Deploying Your Server takes one server from frontmcp build to a connected client, step by step.

frontmcp build [--target <target>] [--config <file>] [--out-dir <dir>] [--entry <file>] [--no-clean]

Reference

frontmcp build

Run it in the project folder. It compiles your TypeScript with the project's tsconfig.json, then does what the target needs.

OptionDefaultWhat it does
-t, --target <target>Every entry of deployments in frontmcp.config, one after the other; node without a confignode, distributed, vercel, lambda, cloudflare, sdk, browser, cli or mcpb.
-c, --config <path>The frontmcp.config found by searching up from the project, or FRONTMCP_CONFIGThe config file to read: its name, version, entry, deployments and transport. Every target honours it since 1.8.7.
-o, --out-dir <dir>distThe base folder. Each target writes to <dir>/<target>/, unless its deployments entry has an outDir.
-e, --entry <file>entry in frontmcp.configThe file with your @FrontMcp class.
--no-cleancleansKeep what's already in the target's folder. By default the build empties it first, so a deleted source file can't linger in the output.
--jscli only: a JavaScript bundle instead of a native executable.
--sea, --merge-from <dir>, --icon <path>, --no-deterministic, --stage-onlymcpb only. See MCP Bundles.

With two entries in deployments, like [{ target: "node" }, { target: "sdk", outDir: "lib" }], one frontmcp build writes dist/node/ and lib/.

The build's name and version come from frontmcp.config. Without a version there, the version is the one in package.json (changed in 1.8.7: it was 1.0.0, whatever package.json said). That's the version in the bundle's name and manifest; the info.version your server reports to clients is the one in @FrontMcp.

Targets

TargetWritesRuns asPage
nodedist/node/<name>.bundle.js, a start script dist/node/<name>, <name>.manifest.json and install-<name>.shAn HTTP server: node dist/node/<name>.bundle.jsNode.js and Docker
distributedThe compiled files, unbundled, plus serverless-setup.js, which sets FRONTMCP_DEPLOYMENT_MODE=distributed (and the endpoint path, and the ha and server.cookies settings of frontmcp.config), and index.js, which loads it and then your serverAn HTTP server on every interface: node dist/distributed/index.jsHigh availability
verceldist/vercel/handler.cjs, one bundle of everything (about 10 MB), and .vercel/output/ in the project for Vercel's Build Output API; vercel.json if there's noneA Node (req, res) handlerVercel
lambdadist/lambda/handler.cjs, one bundle that exports handlerAn AWS Lambda handlerAWS Lambda
cloudflaredist/cloudflare/index.js, a Worker entry, with your compiled files as ES modules; rewrites main in wrangler.tomlA Worker, bundled by wranglerCloudflare Workers
sdkdist/sdk/<name>.cjs.js, <name>.esm.mjs and type declarationsA library that other code imports
browserdist/browser/<name>.browser.mjsAn ES module for a web app's own bundler
cliWith --js, dist/cli/<name>-cli.bundle.js and a start script; without it, a native executable (not built for this page)A command-line program with a serve command, and --stdioConnecting MCP clients
mcpbdist/mcpb/<name>-<version>.mcpb, a ZIP archiveInstalled by a desktop clientConnecting MCP clients

What's in the bundle

TargetsBundledLeft as imports, so they must be installed where it runs
node, cli, sdk, browserYour code, and packages you import that aren't FrontMCP's@frontmcp/sdk, @frontmcp/di, @frontmcp/utils, @frontmcp/auth, @frontmcp/adapters, @frontmcp/lazy-zod, reflect-metadata, the optional better-sqlite3, @frontmcp/storage-sqlite, @vercel/kv, esbuild, @swc/core and fsevents, and @frontmcp/observability and @opentelemetry/sdk-trace-base when they aren't installed
mcpbEverything, FrontMCP included, into server/index.js (about 10 MB), so the archive runs without node_modulesOnly the optional better-sqlite3, @frontmcp/storage-sqlite, @vercel/kv, esbuild, @swc/core and fsevents
vercel, lambdaEverything installed, FrontMCP included, into handler.cjs; an optional package that isn't installed, like @upstash/redis or a model provider's SDK that agents load, stays a require() that runs only if your configuration asks for it. The Lambda handler includes @codegenie/serverless-express since 1.9.better-sqlite3, always; esbuild, @swc/core and React, which FrontMCP only loads when you use them
distributedNothing: tsc outputEverything in dependencies
cloudflareNothing yet: wrangler dev and wrangler deploy bundle itNothing at run time: wrangler puts it all in the Worker

So the node bundle of a small server is a few kilobytes, and it only runs next to a node_modules folder with your production dependencies, FrontMCP's included. @frontmcp/sdk brings what it loads, vectoriadb and tslib among them, since 1.9: the bundle ran in a folder with only @frontmcp/sdk and reflect-metadata installed. (In 1.8.7 vectoriadb came with the frontmcp package, which had to stay in dependencies.)

The vercel and lambda builds bundle what's installed and leave what isn't: in a project made with frontmcp create, with nothing else installed but @codegenie/serverless-express, which lambda needs, both finish without a warning. Changed in 1.9.3: in 1.9.2 they stopped with Module not found: Can't resolve '@upstash/redis' until you installed that package, and warned about @opentelemetry/api; and openai and @anthropic-ai/sdk stayed out of handler.cjs even when installed.

Install a package your server uses, so it's in the bundle: @vercel/kv with redis: { provider: "vercel-kv" }, @frontmcp/observability and @opentelemetry/sdk-trace-base with metrics, openai or @anthropic-ai/sdk for an agent's model. A function that uses one it doesn't have logs it at run time, like @vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv.

Widget sources

A tool whose widget is a file, like ui: { template: { file: join(__dirname, "queue.widget.tsx") } }, reads it when the tool is called, and tsc never emits .widget.tsx or .widget.jsx files. Since 1.8.6, frontmcp build copies them into the output and says copied 1 widget source file (*.widget.tsx/jsx) to dist/node:

TargetsWhere the widget files land
node, cli, vercel, lambdaDirectly in the target's folder, beside the bundle: dist/node/queue.widget.tsx. Every module in a bundle resolves __dirname to that folder. Two widgets with the same file name can't both be there: the build copies neither and says not copying queue.widget.tsx: … share the name.
cloudflare, distributedIn the compiled tree, at the path they have under src/: dist/cloudflare/tools/queue.widget.tsx.
sdk, browser, mcpbNot copied.

Other local files that a widget imports aren't copied, and neither are widgets by install-<name>.sh, which copies a fixed list of files. Before 1.8.6, a built server failed the widget's first call with a bare ENOENT for dist/…/queue.widget.tsx.

Production mode

Production mode hides internal errors from callers and requires MCP_SESSION_SECRET for clients that keep a session (those before MCP 2026-07-28). Whether a build is in production is decided per target:

TargetsIn production when
node, distributed, cli, sdkNODE_ENV=production when the process starts.
vercel, lambdaWhen NODE_ENV=production as the function starts. Vercel sets it for its functions; AWS Lambda doesn't, so set NODE_ENV=production in the function's environment. Without it the function runs in development mode: it makes up secrets, and shows callers the messages of plain errors.
cloudflareWhen NODE_ENV in [vars] is production, which frontmcp create writes, under wrangler dev too (since 1.9). Without it, when wrangler deploy bundles it, and never under wrangler dev. See Cloudflare Workers.

The endpoint path

Where the MCP endpoint is depends on who starts the server:

Started byServes MCP at
frontmcp devhttp.entryPath in @FrontMcp, else transport.http.path in frontmcp.config. A project made with frontmcp create has transport.http.path: "/mcp".
The node build: node dist/node/<name>.bundle.js, which is also what a Docker image's CMD runs, or its start script dist/node/<name>The same: the bundle and the script set FRONTMCP_HTTP_ENTRY_PATH from transport.http.path (or a deployments entry's server.http.entryPath), unless it's already set.
A distributed, vercel, lambda or cloudflare buildhttp.entryPath, else transport.http.path: the build writes it into serverless-setup.js as FRONTMCP_HTTP_ENTRY_PATH.
Anything else: tsx src/main.ts, your own tsc output, createFetchHandler() in your codehttp.entryPath, else FRONTMCP_HTTP_ENTRY_PATH if you set it, else /. frontmcp.config isn't read.

An explicit http.entryPath in @FrontMcp always wins over FRONTMCP_HTTP_ENTRY_PATH. Changed in 1.9: before, node dist/node/<name>.bundle.js served /, whatever frontmcp.config said, so a Docker image answered clients at /mcp with 404. Changed in 1.8.7: before, only frontmcp dev and a cloudflare build served transport.http.path; the node start script, vercel, lambda and distributed served /.

The distributed, vercel, lambda and cloudflare builds end with Server will serve MCP at <path>: the http.entryPath of your entry file, else transport.http.path. Since 1.9 the build reads the entry with the files it imports, so a Cloudflare build with http: { entryPath: "/api" } in a main.ts that imports its app prints /api. (In 1.8.7 it could read the decorator only from an entry that imported no other TypeScript files, and printed /mcp there.)

Ways to run a server

The targets are packaged forms of the entry points FrontMCP has. The environment variables choose between them when the @FrontMcp class is imported; FrontMcpInstance has the details.

Entry pointChosen byUsed byAnswers
HTTP server (bootstrap())Importing the classnode, distributed, cli serve, frontmcp devStreamable HTTP, the older SSE transport, sessions, /healthz, /readyz, /metrics
Unix socketFRONTMCP_DAEMON_SOCKET=<path>, http.socketPath, or runUnixSocket()node, with the variableThe same, on a socket file. See Serving on a Unix socket.
stdioFRONTMCP_STDIO=1, or runStdio()node and cli, with --stdioOne client, on stdin and stdout
Node request handler (createHandler())FRONTMCP_SERVERLESS=1vercel, lambdaLike the HTTP server. These builds also set FRONTMCP_DEPLOYMENT_MODE=serverless, which turns /readyz off.
Web fetch handler (createFetchHandler())FRONTMCP_SERVERLESS=1 and FRONTMCP_WORKER=1cloudflareMCP at one path, fixed health answers, no sessions
In-process (createDirect(), connect())Your codeCalls from your own process, no transport

Caveats

  • frontmcp create pins FrontMCP's packages to the CLI's own version (1.9.3, exactly). With the 1.8.6 CLI they were ~1.8.6, so a new project got the newest 1.8 patch that was published.
  • The node and cli start scripts refuse Node older than 22, or the range in nodeVersion in frontmcp.config.
  • The sdk bundle is your entry file, so importing a bundle built from a file with @FrontMcp starts the HTTP server. Point --entry at a file that exports your apps or configuration instead.
  • The browser bundle is your code only; it imports @frontmcp/sdk and reflect-metadata for your web app's bundler to resolve. It wasn't run for this page. FrontMCP's browser build serves one request at a time.
  • The generated ci/Dockerfile (since 1.8.7), ci/template.yaml (since 1.8.7) and vercel.json (since 1.8.6) match what the builds write. Since 1.9.2 the Dockerfile runs as the node user with a health check (on /health since 1.9.3), and the Lambda template sets NODE_ENV and asks for MCP_SESSION_SECRET: see Node.js and Docker and AWS Lambda.
  • The HTTP server that the node and distributed builds start handles SIGTERM and SIGINT: it finishes requests in flight and exits 0 (stopping). So does a server on a Unix socket, since 1.9.3.

Usage

Everything on this page runs the CLI, so none of it is in the Playground. Each block was run with frontmcp 1.9.3 in a project made with frontmcp create (building every target at once, with 1.9.1), and each result shown is what it printed or served.

Building and running a Node server

npx frontmcp build --target node
[build:exec] bundle created: dist/node/help-desk.bundle.js (6.6 KB)
[build:exec] manifest: dist/node/help-desk.manifest.json
[build:exec] runner: dist/node/help-desk
[build:exec] installer: dist/node/install-help-desk.sh

Start it, next to the project's node_modules, and ask it for its health:

PORT=8080 node dist/node/help-desk.bundle.js
curl http://127.0.0.1:8080/healthz
{"status":"ok","server":{"name":"help-desk","version":"1.0.0"},"runtime":{"platform":"darwin","runtime":"node","deployment":"standalone","env":"development"},"uptime":2.845}

Node.js and Docker covers running it for real: the start script, environment, images and proxies.

Building every target at once

List the targets in frontmcp.config, and frontmcp build builds each of them:

frontmcp.config.ts
import { defineConfig } from "frontmcp";

export default defineConfig({
  name: "help-desk",
  version: "1.2.0",
  entry: "./src/main.ts",
  deployments: [{ target: "node" }, { target: "cloudflare", wrangler: { name: "help-desk" } }],
  transport: { default: "http", http: { port: 3000, path: "/mcp" } },
});
[build] Building 2 target(s) from frontmcp.config: node, cloudflare
[build] ═══ node ═══
[build:exec] bundle created: dist/node/help-desk.bundle.js (6.7 KB)
…
[build] ═══ cloudflare ═══
  Updated wrangler.toml (managed keys only)
Server will serve MCP at /mcp

--target builds one of them, or any other target, whatever the file lists.

Checking a build before you deploy

Most failures that stop a server from starting show up in a Node process too. Run the bundle, or the handler, the way the platform will, with the platform's environment, and look for FrontMCP bootstrap complete (FrontMCP handler created (serverless mode) for a Vercel or Lambda handler) and a 200 from /healthz, then make one MCP call: a missing secret only shows there. Two differences to allow for:

  • Vercel and Lambda bundles read NODE_ENV when they start, and the platform's setting decides: run them with NODE_ENV=production, and test older clients with MCP_SESSION_SECRET set. Vercel and Lambda show how to call them.
  • Run a Cloudflare build with npx wrangler dev, which uses the same runtime as Cloudflare. It's in production when [vars] says NODE_ENV = "production", as the wrangler.toml of frontmcp create does: see checking production locally.

Checks that run while the server is built, like an approval no plugin enforces, fail createDirect() too, so a test can catch them without building at all: see Catching a misconfigured server at startup.


Troubleshooting

Module not found: Can't resolve '@frontmcp/storage-sqlite'

A vercel or lambda build of 1.8.6 or earlier, which stopped at the first optional package that wasn't installed. From 1.8.7 the build leaves such packages out and finishes. If it still stops, upgrade frontmcp. See what's in the bundle.

Module not found: Can't resolve '@upstash/redis'

A vercel or lambda build of 1.9.2 in a project without @upstash/redis. Upgrade frontmcp to 1.9.3, which leaves the package out when it isn't installed, or run npm install @upstash/redis. See what's in the bundle.

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

The Lambda entry adapts Lambda's events with @codegenie/serverless-express, which you install yourself: npm install @codegenie/serverless-express. See AWS Lambda.

[--target cloudflare] config incompatible with Cloudflare Workers

The @FrontMcp source names sqlite, or a redis that isn't written as redis: { provider: "vercel-kv" }, which Workers can't use, or turns on tasks without tasks.redis. See Cloudflare Workers.

Cannot find module '@frontmcp/sdk', or Cannot find module 'reflect-metadata'

The node, cli, sdk and mcpb bundles import FrontMCP's packages instead of containing them. Run the bundle where npm install --omit=dev has installed your dependencies, or copy node_modules next to it, as the Dockerfile does.

A client gets 404 at /mcp after deploying

The server serves /, because the path was only set in frontmcp.config and the server was started without the frontmcp command, or is a node build of 1.8.7 or earlier, which a Docker image runs. Set http: { entryPath: "/mcp" } in @FrontMcp, as above, or set FRONTMCP_HTTP_ENTRY_PATH=/mcp, or point the client at the root. A vercel, lambda, distributed or cloudflare build of 1.8.6 or earlier also served /.

Error: Unknown build target: …

--target takes cli, node, sdk, browser, cloudflare, vercel, lambda, distributed or mcpb. There's no docker target: build node and put it in an image.