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

import { FrontMcp } from '@frontmcp/sdk';
import MyApp from './my.app';

@FrontMcp({
  info: { name: 'My Server', version: '0.1.0' },
  apps: [MyApp],
})
export default class 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)

@FrontMcp({
  /** Required */
  info: {
    name: 'Expense MCP Server',
    version: '1.0.0',
    title?: 'Human title',
    websiteUrl?: 'https://example.com',
    icons?: Icon[], // MCP Icon[]
  },
  apps: [/* App classes */],
Info field descriptions:
FieldDescription
nameServer name shown in MCP discovery
versionSemantic version for clients to track
titleOptional human-readable title
websiteUrlLink to documentation or homepage
iconsMCP Icon array for visual branding
@FrontMcp({
  /** Optional */
  serve?: true,                 // default true (auto-boot)
  splitByApp?: false,           // app composition mode
  providers?: [/* provider classes/factories/values */],

  http?: {
    port?: 3001,                // default 3001
    entryPath?: '',             // MUST match PRM resourcePath in .well-known
    hostFactory?: /* custom host */,
    socketPath?: '/tmp/mcp.sock',  // Unix socket path (overrides port)
    cors?: CorsOptions | false, // default: permissive with built-in adapter (all origins + credentials)
  },

  /** Transport & session lifecycle (NEW in v0.6) */
  transport?: {
    // Session lifecycle
    sessionMode?: 'stateful' | 'stateless',     // default 'stateful'
    platformDetection?: { customOnly?: boolean },

    // Protocol configuration (use preset or custom config)
    protocol?: 'legacy' | 'modern' | 'stateless-api' | 'full' | ProtocolConfig,
    // default: 'legacy' (SSE + streamable + legacy SSE support)

    // Session persistence (Redis-backed, auto-enabled when redis is configured)
    persistence?: false | {
      defaultTtlMs?: 3600000,   // 1 hour
    },

    // Distributed mode
    distributedMode?: boolean | 'auto',  // default: false
  },

  /** Shared Redis config (NEW in v0.6) */
  redis?: {
    host: 'localhost',
    port?: 6379,
    password?: string,
    db?: 0,
    tls?: false,
    keyPrefix?: 'mcp:',
    defaultTtlMs?: 3600000,
  },

  logging?: {
    level?: LogLevel.Info,      // Debug | VERBOSE | Info | Warn | Error | Off
    enableConsole?: true,       // default true
    prefix?: string,
    transports?: [/* custom log transports */],
  },

  /** Server-level default auth (omit if splitByApp: true) */
  auth?: (
    | { type: 'remote', name: string, baseUrl: string, ... }
    | { type: 'local',  id: string,   name: string,   ... }
  ),
})

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

http: {
  port?: number;                // default 3001
  entryPath?: string;           // default ''; MUST match the PRM resourcePath in .well-known
  hostFactory?: FrontMcpServer | ((cfg) => FrontMcpServer);
  socketPath?: string;          // Unix socket path (overrides port)
  cors?: CorsOptions | false;   // default: permissive with built-in adapter (all origins + credentials)
}
FieldDescription
portHTTP listening port (default: 3001)
entryPathJSON-RPC entry path; must match .well-known discovery
hostFactoryCustom host implementation for advanced setups
socketPathUnix socket path; when set, server listens on a socket instead of a TCP port
corsCORS configuration (see CORS below)
  • Port: listening port for Streamable HTTP.
  • 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.
  • 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:
ValueBehavior
undefinedDefault — permissive CORS enabled (origin: true, credentials: false)
falseCORS disabled entirely (no CORS headers are sent)
CorsOptionsCustom CORS configuration
interface CorsOptions {
  origin?:
    | boolean               // true = reflect request origin
    | string                // single allowed origin
    | string[]              // multiple allowed origins
    | ((origin: string | undefined, cb: (err: Error | null, allow?: boolean) => void) => void);
  credentials?: boolean;    // allow cookies / auth headers (default: false)
  maxAge?: number;          // preflight cache duration in seconds
}
FieldTypeDefaultDescription
originboolean | string | string[] | functionAllowed origins; true reflects the request Origin header. Defaults to true only via the built-in DEFAULT_CORS when cors is omitted entirely.
credentialsbooleanfalseWhether to allow credentials (cookies, authorization headers)
maxAgenumberHow long (in seconds) browsers may cache preflight responses
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.
@FrontMcp({
  http: { port: 3001 },
  // cors is undefined → permissive CORS (origin: true, credentials: false)
})

Transport

New in v0.6: Transport configuration has moved from auth.transport and session to a dedicated top-level transport property. This separates transport/session lifecycle concerns from authentication. See Migration below.
The transport config controls session lifecycle, protocol presets, and session persistence. Configure it at the server level or per-app when using splitByApp: true.
@FrontMcp({
  transport: {
    // Session lifecycle
    sessionMode: 'stateful',
    platformDetection: { customOnly: false },

    // Protocol preset (or custom config)
    protocol: 'legacy',  // default - SSE + streamable + legacy SSE support

    // Session persistence (auto-enabled when top-level redis is configured)
    persistence: {
      defaultTtlMs: 3600000,
    },

    // Distributed mode for serverless/multi-instance deployments
    distributedMode: false,
  },
})

Session Lifecycle

OptionDefaultDescription
sessionMode'stateful''stateful' (server-side store) or 'stateless' (JWT-based)
platformDetectionundefinedPlatform detection config; set customOnly: true to skip built-in mappings
transport: {
  sessionMode: 'stateful',    // Server-side session store (default)
}

Protocol Presets

Use protocol presets for simplified configuration, or provide a custom config object for fine-grained control:
PresetDescriptionLegacy SSESSEStreamableJSONStatelessStrict Session
legacyDefault - Modern + legacy SSE support
modernSSE + streamable HTTP with strict sessions
stateless-apiNo sessions, pure request/response
fullAll protocols enabled, maximum compatibility
@FrontMcp({
  transport: {
    protocol: 'legacy',  // Default - backwards compatible
  },
})

Protocol Options (for custom config)

OptionDefault (legacy)Description
ssetrueEnable SSE listener for server-initiated messages
streamabletrueEnable streamable HTTP transport (POST with SSE response)
jsonfalseEnable JSON-only responses (stateful HTTP)
statelessfalseEnable stateless HTTP mode (no session required)
legacytrueEnable legacy SSE transport for older clients
strictSessiontrueRequire session ID for streamable HTTP
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.
@FrontMcp({
  redis: {
    host: 'redis.example.com',
    port: 6379,
  },
  // persistence auto-enabled using global redis config
})
To explicitly disable persistence when redis is configured:
@FrontMcp({
  redis: { host: 'localhost' },
  transport: {
    persistence: false,  // Explicitly disable
  },
})
To customize persistence TTL:
@FrontMcp({
  redis: { host: 'localhost' },
  transport: {
    persistence: {
      defaultTtlMs: 7200000, // 2 hours (default: 1 hour)
    },
  },
})
OptionDefaultDescription
defaultTtlMs3600000Session TTL in milliseconds (1 hour)
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

New in v0.6: Redis configuration has moved to 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:
@FrontMcp({
  redis: {
    host: 'redis.example.com',
    port: 6379,
    password: 'secret',
    db: 0,
    tls: true,
    keyPrefix: 'mcp:',
    defaultTtlMs: 3600000,
  },
})
OptionDefaultDescription
host(required)Redis server hostname
port6379Redis server port
password-Optional authentication password
db0Redis database index
tlsfalseEnable TLS/SSL connection
keyPrefix'mcp:'Prefix for all Redis keys
defaultTtlMs3600000Default TTL for cached data (1 hour)

Usage with Features

The redis config is automatically used by:
  • Session persistence (transport.persistence) — stores session state for recovery
  • Token storage (auth.tokenStorage: { type: 'redis' }) — stores refresh tokens securely
@FrontMcp({
  redis: {
    host: 'localhost',
    keyPrefix: 'myapp:',
  },
  transport: {
    persistence: { defaultTtlMs: 3600000 },
  },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    tokenStorage: { type: 'redis' },
  },
})

Migration from auth.transport

Deprecated: The auth.transport and session properties are deprecated and will be removed in v1.0.0. Migrate to the top-level transport config.
FrontMCP automatically migrates old configs at runtime with a deprecation warning. Update your config manually to remove the warning:
@FrontMcp({
  session: {
    sessionMode: 'stateful',
  },
  auth: {
    mode: 'orchestrated',
    type: 'local',
    transport: {
      enableStreamableHttp: true,
      enableStatefulHttp: true,
      recreation: {
        enabled: true,
        redis: { host: 'localhost' },
      },
    },
  },
})

Migration Mapping

Old PathNew Path
session.sessionModetransport.sessionMode
session.platformDetectiontransport.platformDetection
auth.transport.enableLegacySSEtransport.protocol.legacy
auth.transport.enableSseListenertransport.protocol.sse
auth.transport.enableStreamableHttptransport.protocol.streamable
auth.transport.enableStatelessHttptransport.protocol.stateless
auth.transport.enableStatefulHttptransport.protocol.json
auth.transport.requireSessionForStreamabletransport.protocol.strictSession
auth.transport.recreationtransport.persistence
auth.transport.recreation.redisredis (top-level)
Instead of individual flags, use a protocol preset: 'legacy' (default), 'modern', 'stateless-api', or 'full'.

Logging

logging: {
  level?: LogLevel;        // default Info
  enableConsole?: boolean; // default true
  prefix?: string;
  transports?: LogTransportType[]; // custom sinks via @FrontMcpLogTransport
}
FieldDescription
levelMinimum log level: Debug, Verbose, Info, Warn, Error, Off
enableConsoleWhether to output to stdout (default: true)
prefixOptional prefix for all log messages
transportsCustom log transport implementations
Use custom log transports for shipping logs to external systems; console remains on by default.

Global providers

providers: [
  /* Provider classes/factories/values */
];
Define DI-style singletons available to all apps (and their tools/plugins). Scopes are supported at the provider level (GLOBAL, SESSION, REQUEST).

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)

auth: {
  type: 'remote',
  name: 'frontegg',
  baseUrl: 'https://auth.example.com',
  dcrEnabled?: boolean,
  clientId?: string | ((info) => string), // for non-DCR via local proxy
  mode?: 'orchestrated' | 'transparent',
  allowAnonymous?: boolean,
  consent?: boolean,
  scopes?: string[],
  grantTypes?: ['authorization_code','refresh_token'],
  authEndpoint?: string,
  tokenEndpoint?: string,
  registrationEndpoint?: string,
  userInfoEndpoint?: string,
  jwks?: JSONWebKeySet,
  jwksUri?: string,
}

Local OAuth (built-in AS)

auth: {
  type: 'local',
  id: 'local',
  name: 'Local Auth',
  scopes?: string[],
  grantTypes?: ['authorization_code','refresh_token'],
  allowAnonymous?: boolean,  // default true
  consent?: boolean,         // show tool/resource/prompt consent
  jwks?: JSONWebKeySet,      // inline keys (optional)
  signKey?: JWK | Uint8Array // private key (optional; auto-gen if omitted)
}
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 stateful session mode with Redis for 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 stateless session mode 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.