Skip to main content
This page covers Server Mode (HTTP). For embedded SDK usage or serverless handlers, see Runtime Modes.
FrontMCP servers are defined with a single decorator, @FrontMcp({ ... }). This page shows the minimal config and then every top-level option you can use. Deep dives live in the pages listed under Servers.

Minimal server

Required:
  • info.name (string)
  • info.version (string)
  • apps (at least one app)
Everything else is optional with sensible defaults.

Full configuration (at a glance)

Info field descriptions:

Composition mode

FrontMCP can host many apps. Choose how they’re exposed:
  • Multi-App (default): splitByApp: false One server scope. You may configure server-level auth and all apps inherit it (apps can still override with app-level auth).
  • Split-By-App: splitByApp: true Each app is isolated under its own scope/base path (for example /billing). Streamable HTTP, the /message SSE endpoint, and OAuth issuers reuse that scope automatically. Server-level auth is disallowed; configure auth per app. (See Authentication → Overview.)
If you’re offering multiple products or tenants, splitByApp: true gives clean separation and per-app auth.

HTTP transport

  • Port: listening port for Streamable HTTP. Defaults to process.env.PORT or 3000 when omitted.
  • entryPath: your MCP JSON-RPC entry ('' or '/mcp'). Must align with discovery.
  • hostFactory: advanced — provide/construct a custom host implementation.
  • socketPath: listen on a Unix socket instead of a TCP port. The entire HTTP feature set (streamable HTTP, SSE, elicitation, sessions) works unchanged over Unix sockets.
  • routes: mount custom HTTP handlers on the same listener as the MCP endpoint (downloads, webhooks, extra health probes). See Custom HTTP routes.
  • Split-by-app scopes: when splitByApp is enabled, clients hit <entryPath>/<appId> (for example /mcp/billing) and subscribe via <entryPath>/<appId>/message; FrontMCP handles the prefixing.

CORS

CORS is configured via the http.cors option. It supports three modes:
The permissive default (origin: true, credentials: false) only applies when using the built-in Express adapter (i.e. no hostFactory). When a custom hostFactory is provided, the factory receives the full http config (including cors) but is responsible for applying CORS itself — FrontMCP does not install CORS middleware automatically.
An empty cors: {} object (without an origin key) will not enable CORS middleware — the adapter requires an explicit origin value. To enable permissive CORS, omit cors entirely or pass cors: { origin: true }.
FrontMCP automatically adds Mcp-Session-Id (alongside WWW-Authenticate) to the Access-Control-Expose-Headers response header when CORS is enabled. This ensures Streamable HTTP clients can read the session ID from cross-origin responses without additional configuration.

Custom HTTP routes

http.routes mounts first-class custom HTTP handlers on the same listener as the MCP JSON-RPC endpoint. They share the configured CORS policy, body limits, and security middleware. Use them for byte delivery (file/stream/binary downloads), webhooks, health probes beyond /health, or any non-JSON-RPC surface the MCP channel cannot serve.
req / res are exported as ServerRequest / ServerResponse from @frontmcp/sdk; ServerRequestHandler types the handler. Respond with res.status(...).json(...) / res.send(...), or call next() to fall through.

auth opt-in

Routes are public by default. With auth: true, the request runs through the same session:verify flow as the MCP endpoint:
  • Unauthorized401 + WWW-Authenticate header.
  • Forbidden (valid token, insufficient scope) → 403 + WWW-Authenticate.
  • Authorized → the verified authorization is attached to req.authSession before the handler runs.
In public auth mode (or transparent with allowAnonymous), auth: true routes get an anonymous session and the handler still runs. Under modes with no anonymous fallback (e.g. local with allowDefaultPublic: false), unauthenticated requests receive the 401.

Reserved-path guard

Custom paths that collide with FrontMCP’s own surfaces are rejected at startup (fail-fast): the resolved MCP entry path (and its /sse + /message siblings, including split-by-app bases like /mcp/billing), anything under /oauth/* and /.well-known/*, and /health + /metrics. Pick a non-reserved path.

Content-Type gotcha

The built-in Express adapter defaults every response to application/json; charset=utf-8. HTML, binary, and streaming handlers MUST set their own content type via res.setHeader('Content-Type', ...) (or res.type(...)) before sending the body. hostFactory users are not affected — they own their Express app.

Large payloads: prefer a custom route over @Resource

When a tool needs to hand the client a large payload, return a resource_link that points at a custom http.routes GET handler, and serve the bytes from that handler (it supports stream/binary delivery). A @Resource rides the MCP JSON-RPC channel and is not out-of-band — large reads block the protocol stream and inflate token usage. The custom route delivers bytes on a separate HTTP request the client fetches directly.

Transport

Transport configuration is a dedicated top-level transport property, separate from auth. It controls session lifecycle, protocol presets, and persistence.
The transport config controls session lifecycle, protocol presets, and session persistence. Configure it at the server level or per-app when using splitByApp: true.

Session Lifecycle

Protocol Presets

Use protocol presets for simplified configuration, or provide a custom config object for fine-grained control:

Protocol Options (for custom config)

Use protocol: 'legacy' (the default) for maximum backwards compatibility with older MCP clients. Use protocol: 'modern' when you only need to support newer Streamable HTTP clients.

Session Persistence

Session persistence is automatically enabled when you configure top-level redis. Sessions are persisted to Redis/Vercel KV and transports can be recreated after server restart.
To explicitly disable persistence when redis is configured:
To customize persistence TTL:
Session persistence uses the top-level redis config. Configure redis once and it’s automatically shared by transport.persistence and auth.tokenStorage.
You can apply different transport policies per app when splitByApp: true. Sensitive apps stay stream-only with strict session IDs, while demo or health-check apps can enable stateful/stateless HTTP for easier automation.

Redis

Redis configuration is a dedicated top-level redis property, shared by both transport.persistence and auth.tokenStorage.
Configure Redis once at the server level for use across features:

Usage with Features

The redis config is automatically used by:
  • Session persistence (transport.persistence) — stores session state for recovery
  • Token storage (auth.tokenStorage: { redis: { ... } }) — stores refresh tokens securely

Instead of individual flags, use a protocol preset: 'legacy' (default), 'modern', 'stateless-api', or 'full'.

Logging

Use custom log transports for shipping logs to external systems; console remains on by default.

Global providers

Define DI-style singletons available to all apps (and their tools/plugins). Provider lifetime scopes: 'global' (default, shared across all requests) or 'context' (one instance per execution context).

Authentication (server level)

Server-level auth sets the default auth for all apps (unless splitByApp: true, where auth must be per-app).

Remote OAuth (encapsulated external IdP)

Local OAuth (built-in AS)

Apps can also define their own auth (and mark themselves standalone) to expose an isolated auth surface — useful when mixing public and private apps under one server.

Bootstrapping & discovery

  • Version safety: on boot, FrontMCP checks that all @frontmcp/* packages are aligned and throws a clear “version mismatch” error otherwise.
If you disable serve, you’re responsible for calling the core bootstrap yourself.

Common starting points

  • Single app, default everything: minimal sample above.
  • Multiple apps, shared auth: omit splitByApp, set server-level auth.
  • Isolated apps with per-app auth: set splitByApp: true, configure auth in each app.

Best Practices

Do:
  • Start with minimal config and add options as needed
  • Use splitByApp: true for multi-tenant deployments
  • Use top-level transport config (not auth.transport or session)
  • Use protocol presets ('legacy', 'modern', etc.) instead of individual flags
  • Configure redis at top-level for shared use across features (auto-enables persistence)
  • Use Redis for session persistence in production deployments
  • Set log level to Info or higher in production
Don’t:
  • Configure server-level auth when using splitByApp: true
  • Use deprecated auth.transport or session — migrate to transport
  • Disable serve unless you’re managing bootstrap manually
  • Use protocol: 'stateless-api' with short-lived upstream tokens
  • Use protocol: 'stateless-api' in production without trust boundaries
  • Leave enableConsole: true in containerized production (use transports)

Next up: learn how to structure Apps, Tools, Resources, and Prompts in the Core Components section.