> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The FrontMCP Server

> The core FrontMCP server — start with the minimum and scale up with HTTP, sessions, logging, providers, and authentication.

<Note>
  This page covers **Server Mode** (HTTP). For embedded SDK usage or serverless handlers, see [Runtime Modes](/frontmcp/deployment/runtime-modes).
</Note>

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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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)

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@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:**

| Field        | Description                           |
| ------------ | ------------------------------------- |
| `name`       | Server name shown in MCP discovery    |
| `version`    | Semantic version for clients to track |
| `title`      | Optional human-readable title         |
| `websiteUrl` | Link to documentation or homepage     |
| `icons`      | MCP Icon array for visual branding    |

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  /** Optional */
  serve?: true,                 // default true (auto-boot)
  splitByApp?: false,           // app composition mode
  providers?: [/* provider classes/factories/values */],

  http?: {
    port?: 3000,                // default: process.env.PORT or 3000
    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 (origin: true, credentials: false)
  },

  /** Transport & session lifecycle (NEW in v0.6) */
  transport?: {
    // Session lifecycle
    // sessionMode has been removed in v1.0 — configure via transport.protocol presets
    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,
  },

  /** Default ESM package loader config (affects all App.esm() apps) */
  loader?: {
    url?: string,           // Base URL for registry + bundle fetching
    registryUrl?: string,   // Separate registry URL (if different)
    token?: string,         // Bearer token for auth
    tokenEnvVar?: string,   // Env var name containing bearer token (takes precedence over `token`)
  },

  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?: (
    | { mode: 'public', ... }
    | { mode: 'transparent', ... }
    | { mode: 'local', ... }
    | { mode: 'remote', provider: 'https://auth.example.com', ... }
  ),
})
```

***

## 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*.)

<Tip>If you’re offering multiple products or tenants, `splitByApp: true` gives clean separation and per-app auth.</Tip>

***

## HTTP transport

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
http: {
  port?: number;                // default: process.env.PORT or 3000
  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 (origin: true, credentials: false)
}
```

| Field         | Description                                                                  |
| ------------- | ---------------------------------------------------------------------------- |
| `port`        | HTTP listening port (default: `process.env.PORT` or `3000`)                  |
| `entryPath`   | JSON-RPC entry path; must match `.well-known` discovery                      |
| `hostFactory` | Custom host implementation for advanced setups                               |
| `socketPath`  | Unix socket path; when set, server listens on a socket instead of a TCP port |
| `cors`        | CORS configuration (see [CORS](#cors) below)                                 |

* **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.
* **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:

| Value         | Behavior                                                                     |
| ------------- | ---------------------------------------------------------------------------- |
| `undefined`   | **Default** — permissive CORS enabled (`origin: true`, `credentials: false`) |
| `false`       | CORS disabled entirely (no CORS headers are sent)                            |
| `CorsOptions` | Custom CORS configuration                                                    |

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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
}
```

| Field         | Type                                        | Default | Description                                                                                                                                            |
| ------------- | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `origin`      | `boolean \| string \| string[] \| function` | —       | Allowed origins; `true` reflects the request `Origin` header. Defaults to `true` only via the built-in `DEFAULT_CORS` when `cors` is omitted entirely. |
| `credentials` | `boolean`                                   | `false` | Whether to allow credentials (cookies, authorization headers)                                                                                          |
| `maxAge`      | `number`                                    | —       | How long (in seconds) browsers may cache preflight responses                                                                                           |

<Warning>
  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.
</Warning>

<Tip>
  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 }`.
</Tip>

<Info>
  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.
</Info>

<CodeGroup>
  ```ts Default (permissive) theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    http: { port: 3001 },
    // cors is undefined → permissive CORS (origin: true, credentials: false)
  })
  ```

  ```ts Disabled theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    http: {
      port: 3001,
      cors: false,  // No CORS headers
    },
  })
  ```

  ```ts Custom theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    http: {
      port: 3001,
      cors: {
        origin: ['https://app.example.com', 'https://admin.example.com'],
        credentials: true,
        maxAge: 86400,  // cache preflight for 24 hours
      },
    },
  })
  ```
</CodeGroup>

***

## Transport

<Info>
  Transport configuration is a dedicated top-level `transport` property, separate from `auth`. It controls session lifecycle, protocol presets, and persistence.
</Info>

The `transport` config controls session lifecycle, protocol presets, and session persistence. Configure it at the server level or per-app when using `splitByApp: true`.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  transport: {
    // Session lifecycle
    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

| Option              | Default     | Description                                                                 |
| ------------------- | ----------- | --------------------------------------------------------------------------- |
| `platformDetection` | `undefined` | Platform detection config; set `customOnly: true` to skip built-in mappings |

### Protocol Presets

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

| Preset          | Description                                  | Legacy SSE | SSE | Streamable | JSON | Stateless | Strict Session |
| --------------- | -------------------------------------------- | ---------- | --- | ---------- | ---- | --------- | -------------- |
| `legacy`        | **Default** - Modern + legacy SSE support    | ✅          | ✅   | ✅          | ❌    | ❌         | ✅              |
| `modern`        | SSE + streamable HTTP with strict sessions   | ❌          | ✅   | ✅          | ❌    | ❌         | ✅              |
| `stateless-api` | No sessions, pure request/response           | ❌          | ❌   | ❌          | ❌    | ✅         | ❌              |
| `full`          | All protocols enabled, maximum compatibility | ✅          | ✅   | ✅          | ✅    | ✅         | ❌              |

<CodeGroup>
  ```ts Using a Preset theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    transport: {
      protocol: 'legacy',  // Default - backwards compatible
    },
  })
  ```

  ```ts Custom Protocol Config theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    transport: {
      protocol: {
        sse: true,
        streamable: true,
        json: true,        // Enable JSON-only responses
        stateless: false,
        legacy: true,
        strictSession: false,
      },
    },
  })
  ```
</CodeGroup>

### Protocol Options (for custom config)

| Option          | Default (legacy) | Description                                               |
| --------------- | ---------------- | --------------------------------------------------------- |
| `sse`           | `true`           | Enable SSE listener for server-initiated messages         |
| `streamable`    | `true`           | Enable streamable HTTP transport (POST with SSE response) |
| `json`          | `false`          | Enable JSON-only responses (stateful HTTP)                |
| `stateless`     | `false`          | Enable stateless HTTP mode (no session required)          |
| `legacy`        | `true`           | Enable legacy SSE transport for older clients             |
| `strictSession` | `true`           | Require session ID for streamable HTTP                    |

<Tip>
  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.
</Tip>

### 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.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  redis: {
    host: 'redis.example.com',
    port: 6379,
  },
  // persistence auto-enabled using global redis config
})
```

To explicitly disable persistence when redis is configured:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  redis: { host: 'localhost' },
  transport: {
    persistence: false,  // Explicitly disable
  },
})
```

To customize persistence TTL:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  redis: { host: 'localhost' },
  transport: {
    persistence: {
      defaultTtlMs: 7200000, // 2 hours (default: 1 hour)
    },
  },
})
```

| Option         | Default   | Description                          |
| -------------- | --------- | ------------------------------------ |
| `defaultTtlMs` | `3600000` | Session TTL in milliseconds (1 hour) |

<Info>
  Session persistence uses the top-level `redis` config. Configure `redis` once and it's automatically shared by `transport.persistence` and `auth.tokenStorage`.
</Info>

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

<Info>
  Redis configuration is a dedicated top-level `redis` property, shared by both `transport.persistence` and `auth.tokenStorage`.
</Info>

Configure Redis once at the server level for use across features:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  redis: {
    host: 'redis.example.com',
    port: 6379,
    password: 'secret',
    db: 0,
    tls: true,
    keyPrefix: 'mcp:',
    defaultTtlMs: 3600000,
  },
})
```

| Option         | Default    | Description                          |
| -------------- | ---------- | ------------------------------------ |
| `host`         | (required) | Redis server hostname                |
| `port`         | `6379`     | Redis server port                    |
| `password`     | -          | Optional authentication password     |
| `db`           | `0`        | Redis database index                 |
| `tls`          | `false`    | Enable TLS/SSL connection            |
| `keyPrefix`    | `'mcp:'`   | Prefix for all Redis keys            |
| `defaultTtlMs` | `3600000`  | Default 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: { redis: { ... } }`) — stores refresh tokens securely

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  redis: {
    host: 'localhost',
    keyPrefix: 'myapp:',
  },
  transport: {
    persistence: { defaultTtlMs: 3600000 },
  },
  auth: {
    mode: 'local',
    id: 'local',
    name: 'Local Auth',
    tokenStorage: { redis: { host: 'localhost' } },
  },
})
```

***

<Tip>
  Instead of individual flags, use a **protocol preset**: `'legacy'` (default), `'modern'`, `'stateless-api'`, or `'full'`.
</Tip>

***

## Logging

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
logging: {
  level?: LogLevel;        // default Info
  enableConsole?: boolean; // default true
  prefix?: string;
  transports?: LogTransportType[]; // custom sinks via @FrontMcpLogTransport
}
```

| Field           | Description                                               |
| --------------- | --------------------------------------------------------- |
| `level`         | Minimum log level: Debug, Verbose, Info, Warn, Error, Off |
| `enableConsole` | Whether to output to stdout (default: true)               |
| `prefix`        | Optional prefix for all log messages                      |
| `transports`    | Custom log transport implementations                      |

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

***

## Global providers

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
providers: [
  /* Provider classes/factories/values */
];
```

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)

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'remote',
  provider: 'https://auth.example.com',   // OAuth provider base URL (required)
  clientId?: string,
  clientSecret?: string,
  scopes?: string[],
  providerConfig?: {
    dcrEnabled?: boolean,
    authEndpoint?: string,
    tokenEndpoint?: string,
    // ...other advanced provider fields
  },
  tokenStorage?: 'memory' | { redis: RedisConfig }, // default: 'memory'
  consent?: ConsentConfig,
  publicAccess?: PublicAccessConfig,
  refresh?: TokenRefreshConfig,
  expectedAudience?: string | string[],
}
```

### Local OAuth (built-in AS)

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'local',
  local?: {                  // local signing config (optional)
    issuer?: string,
    signingKey?: JWK | Uint8Array,
    jwks?: JSONWebKeySet,
  },
  tokenStorage?: 'memory' | { redis: RedisConfig }, // default: 'memory'
  consent?: ConsentConfig,
  publicAccess?: PublicAccessConfig,
  allowDefaultPublic?: boolean, // default false
  anonymousScopes?: string[],   // default ['anonymous']
}
```

<Info>
  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.
</Info>

***

## Bootstrapping & discovery

* **Version safety**: on boot, FrontMCP checks that all `@frontmcp/*` packages are aligned and throws a clear “version mismatch” error otherwise.

<Warning>If you disable `serve`, you’re responsible for calling the core bootstrap yourself.</Warning>

***

## 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.
