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 handlerReference
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
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
| Request | Answer |
|---|---|
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 /health | 200, with "deployment": "serverless" and "env": "production". |
GET /readyz | 404: 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:
@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 lambdawritesci/template.yamlwithCodeUri: ../dist/lambda/andHandler: handler.handler, which find the handler since 1.8.7 (before,CodeUri: ../dist/andHandler: main.handler, a file that doesn't exist), and run since 1.9. Since 1.9.2 it also setsNODE_ENV: productionandMCP_SESSION_SECRET(below); a template written by an oldercreatesets 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
frontmcp1.9.3, the build and the handler were checked by callinghandler.cjs, alone in an empty folder, from Node with HTTP API (2.0) events:/healthz,/readyz, atools/callat/mcpand aninitialize, with and withoutNODE_ENV=production, and withredispointed 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 leavesNODE_ENVunset wasn't checked on AWS.
Usage
Building the handler
npm install @codegenie/serverless-express # frontmcp create --target lambda already did
npx frontmcp build --target lambdadist/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
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:
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.cjs200 {"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.