AWS Lambda

frontmcp build --target lambda bundles your server, FrontMCP and all, into one file that exports a Lambda handler. The handler turns API Gateway and function URL events into requests for FrontMCP's Express app, through @codegenie/serverless-express, which you install and the build puts in the bundle. Like every serverless build, it keeps nothing between invocations unless you give it Redis, and answers /healthz but not /readyz. It's in production mode when NODE_ENV=production, which Lambda doesn't set: set it in the function's environment.

npm install @codegenie/serverless-express
frontmcp build --target lambda    # dist/lambda/handler.cjs, exports handler

Reference

What the build writes

dist/lambda/
  main.js, help-desk.app.js, …   your compiled code
  serverless-setup.js            sets FRONTMCP_SERVERLESS=1, FRONTMCP_DEPLOYMENT_MODE=serverless and FRONTMCP_HTTP_ENTRY_PATH
  index.js                       the entry: loads the setup and your server, exports handler
  handler.cjs                    index.js and everything it imports, in one file (about 10 MB)
  queue.widget.tsx, …            your tools' *.widget.tsx and *.widget.jsx files, copied beside handler.cjs
dist/lambda/index.js
require('./serverless-setup.js');
require('./main.js');
const { getServerlessHandlerAsync } = require('@frontmcp/sdk');
const serverlessExpressMod = require('@codegenie/serverless-express');
const serverlessExpress = serverlessExpressMod.default || serverlessExpressMod;

let serverlessExpressInstance = null;

async function setup() {
  const app = await getServerlessHandlerAsync();
  serverlessExpressInstance = serverlessExpress({ app });
}

exports.handler = async (event, context) => {
  if (!serverlessExpressInstance) {
    await setup();
  }
  return serverlessExpressInstance(event, context);
};

The first invocation of a new instance builds the server; later ones reuse it.

What to deploy

handler.cjs contains FrontMCP, your dependencies and @codegenie/serverless-express, so it needs no node_modules: copied alone into an empty folder, it answered /healthz, a tools/call and an initialize, and reached Redis with redis set. Deploy dist/lambda/ as it is, or handler.cjs on its own. The function's handler is handler.handler, on the nodejs24.x runtime that frontmcp create picks; the ci/template.yaml it writes has that handler, and CodeUri: ../dist/lambda/. A tool with a widget file reads it beside handler.cjs when it's called, so deploy the *.widget.tsx files that the build put next to it in dist/lambda/ too (widget sources).

The build needs @codegenie/serverless-express installed in the project, which frontmcp create --target lambda adds, and stops without it: missing required peer dependency. Optional packages that aren't installed, like @upstash/redis, stay out of the bundle, and the build finishes without a warning. Changed in 1.9.3: in 1.9.2 the build stopped on @upstash/redis (below). See what's in the bundle.

Changed in 1.9: before, handler.cjs left @codegenie/serverless-express out, so a function deployed from dist/lambda/ failed on load with Cannot find module '@codegenie/serverless-express' unless you shipped the package beside it.

What the function serves

RequestAnswer
MCP at http.entryPath, or /As FrontMCP's Node server answers it. A response that streams, like initialize from a client before MCP 2026-07-28, arrives whole, as one body of text/event-stream events.
GET /healthz, GET /health200, with "deployment": "serverless" and "env": "production".
GET /readyz404: readiness checks are off in serverless mode.

The handler took both API Gateway event formats: HTTP APIs and function URLs (payload version 2.0), and REST APIs (1.0).

Like the Vercel bundle, it reads NODE_ENV when it starts, and Lambda doesn't set it: without NODE_ENV=production in the function's environment, /healthz says "env": "development", and the function makes up the secrets it should require and shows callers the messages of plain errors. In production, give it MCP_SESSION_SECRET if clients on protocol versions before 2026-07-28 use it: without it their initialize answers 500 with {"error":"server_misconfigured","code":"SESSION_SECRET_REQUIRED",…}, while 2026-07-28 calls and /healthz answer 200 (changed in 1.8.5; before, every anonymous or static-key call got the 500). local and remote auth need JWT_SECRET too.

transport.http.path in frontmcp.config reaches the function: a project made with frontmcp create serves /mcp, and http.entryPath in @FrontMcp wins over it. A request to / is a 404. Route the API's /{proxy+} to the function either way. Changed in 1.8.7: the handler used to be always in production, whatever NODE_ENV said, and served / unless http.entryPath was set.

Storage

Lambda runs many instances, and each keeps its own memory. Clients on MCP 2026-07-28 need nothing more. Sessions of older clients, background tasks and pending questions need a Redis every instance can reach, like ElastiCache in the function's VPC:

main.ts
@FrontMcp({
  info: { name: "help-desk", version: "1.0.0" },
  apps: [HelpDesk],
  redis: { url: process.env.REDIS_URL! }, // like rediss://default:…@desk-cache.example.com:6379
})
export default class Server {}

Redis has every option. The decorator runs when the instance loads the bundle, so process.env in it reads the function's environment variables.

Caveats

  • frontmcp create --target lambda writes ci/template.yaml with CodeUri: ../dist/lambda/ and Handler: handler.handler, which find the handler since 1.8.7 (before, CodeUri: ../dist/ and Handler: main.handler, a file that doesn't exist), and run since 1.9. Since 1.9.2 it also sets NODE_ENV: production and MCP_SESSION_SECRET (below); a template written by an older create sets neither.
  • The startup log warns SSE transport is not supported in serverless deployments, and without Redis, No distributed storage backend detected in production. Using in-memory storage.
  • This site couldn't deploy to AWS. With frontmcp 1.9.3, the build and the handler were checked by calling handler.cjs, alone in an empty folder, from Node with HTTP API (2.0) events: /healthz, /readyz, a tools/call at /mcp and an initialize, with and without NODE_ENV=production, and with redis pointed at Valkey 8. A call at / was last checked with 1.9.1, REST API (1.0) events with 1.8.4, SAM wasn't run, and that Lambda leaves NODE_ENV unset wasn't checked on AWS.

Usage

Building the handler

npm install @codegenie/serverless-express   # frontmcp create --target lambda already did
npx frontmcp build --target lambda

dist/lambda/ is what Lambda runs. Only handler.cjs, and the widget files beside it, are used: the compiled files next to them are already in the bundle.

Deploying with SAM

ci/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: help-desk - FrontMCP Lambda Function

Parameters:
  McpSessionSecret:
    Type: String
    NoEcho: true
    Description: MCP_SESSION_SECRET for session-ID encryption, required in production. Generate it once (openssl rand -hex 32) and pass the same value on every deploy; a new value ends every open session

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs24.x
    MemorySize: 256
    Environment:
      Variables:
        NODE_ENV: production
        MCP_SESSION_SECRET: !Ref McpSessionSecret

Resources:
  FrontMCPFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../dist/lambda/
      Handler: handler.handler
      Events:
        ApiEvent:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY

Outputs:
  ApiEndpoint:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"

This is the template frontmcp create --target lambda writes. NODE_ENV: production puts the function in production mode, and McpSessionSecret is a required parameter, marked NoEcho so CloudFormation doesn't show its value: sam deploy needs a value for it, which belongs in your secret store, not in a file in the repository. Generate it once and pass the same value on every deploy: an id made under another secret gets 404 invalid session id, so a new value ends every open session. The project's deploy script runs cd ci && sam build && sam deploy. SAM wasn't run for this page.

Calling the handler locally

A test can call handler with an event, the way Lambda does, before anything is deployed:

invoke.cjs
const { handler } = require("./dist/lambda/handler.cjs");

const event = {
  version: "2.0",
  routeKey: "$default",
  rawPath: "/healthz",
  rawQueryString: "",
  headers: { host: "abc123.execute-api.eu-west-1.amazonaws.com" },
  requestContext: { http: { method: "GET", path: "/healthz", protocol: "HTTP/1.1", sourceIp: "203.0.113.7", userAgent: "test" }, requestId: "1", routeKey: "$default", stage: "$default" },
  isBase64Encoded: false,
};

handler(event, { awsRequestId: "1", getRemainingTimeInMillis: () => 30000 }).then((response) => {
  console.log(response.statusCode, response.body);
});
NODE_ENV=production node invoke.cjs
200 {"status":"ok","server":{"name":"help-desk","version":"1.0.0"},"runtime":{"platform":"darwin","runtime":"node","deployment":"serverless","env":"production"},"uptime":0.061}

A POST event with an MCP body and the 2026-07-28 headers (request headers) returns the tool's result the same way.


Troubleshooting

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

The build checks that the package is installed in the project. npm install @codegenie/serverless-express.

Cannot find module '@codegenie/serverless-express'

The handler was built by FrontMCP 1.8.7 or earlier, which left the package out of the bundle. Build it again with 1.9.3, or deploy node_modules/@codegenie/serverless-express next to it. See what to deploy.

The function can't find its handler

The function's handler names a file that isn't in the package. With the folder above it's handler.handler. A template that an older frontmcp create wrote uses main.handler in dist/, which the build doesn't produce.

initialize answers 500, with "error":"server_misconfigured" and SESSION_SECRET_REQUIRED

MCP_SESSION_SECRET isn't set, the function is in production (NODE_ENV=production), and a client on a protocol version before 2026-07-28 tried to open a session. /healthz and 2026-07-28 calls still answer 200, so a health check doesn't catch it.

/healthz says "env": "development"

The function's environment has no NODE_ENV=production, and Lambda doesn't set it. Add it, as in the template, which frontmcp create writes with it since 1.9.2.

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

A lambda build of 1.9.2, which stopped on this optional package when it wasn't installed. Upgrade frontmcp to 1.9.3, which leaves it out, or npm install @upstash/redis, which bundles it into handler.cjs. See what's in the bundle.

/readyz answers 404

Readiness checks are off in serverless mode. Use /healthz.

Older clients get 404 session not initialized

Their next request reached another instance, or a new one. Give the server a Redis every instance can reach. 404 invalid session id instead means the id was made under another MCP_SESSION_SECRET: every copy of the function needs the same one.