---
title: Configuration File
description: Configure FrontMCP projects with frontmcp.config.ts for multi-target builds, server settings, security headers, and HA
icon: 'gear'
keywords: ['frontmcp', 'mcp', 'model context protocol', 'deployment', 'production', 'hosting', 'config', 'frontmcp.config', 'configuration', 'typescript']
---

FrontMCP uses a typed configuration file to define project settings, deployment targets, server configuration, and security policies. The file supports TypeScript, JavaScript, and JSON formats with full IDE autocomplete.

## Quick Start

Create `frontmcp.config.ts` in your project root:

```typescript
import { defineConfig } from 'frontmcp';

export default defineConfig({
  name: 'my-server',
  deployments: [{ target: 'node' }],
});
```

The `defineConfig()` helper is a pass-through that enables IDE type hints and autocomplete.

## File Resolution Order

FrontMCP locates the config file in this precedence order (issue #400):

1. Explicit `--config <path>` flag on any command.
2. `FRONTMCP_CONFIG` env var.
3. Upward walk from `cwd` to the nearest ancestor containing a `frontmcp.config.*` file (caps at 10 levels — monorepo nested apps no longer require `cd <repo-root>`).
4. Fallback: derives minimal config from `package.json` (name, default node target).

Within a directory, the matching extensions are tried in this order:

1. `frontmcp.config.ts`
2. `frontmcp.config.js`
3. `frontmcp.config.json`
4. `frontmcp.config.mjs`
5. `frontmcp.config.cjs`

## Override precedence

For every CLI option that's also expressible in the config, the effective value is computed as:

```
explicit CLI flag  >  FRONTMCP_<NAME> env var  >  frontmcp.config field  >  built-in default
```

Example: `frontmcp dev --port 5000` always wins, regardless of `transport.http.port` in the config. With no flag, the resolver reads `transport.http.port`, then falls back to the framework default (3000).

<Note>
  When using JSON format, add `"$schema"` for autocomplete in VS Code and WebStorm:
  ```json
  {
    "$schema": "./node_modules/@frontmcp/cli/frontmcp.schema.json",
    "name": "my-server",
    "deployments": [{ "target": "node" }]
  }
  ```
</Note>

## Top-Level Fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Server name (kebab-case, no spaces) |
| `version` | string | No | Server version |
| `entry` | string | No | Custom entry file path (consumed by `dev`, `inspector`, `pm start/socket`) |
| `nodeVersion` | string | No | Target Node.js version |
| `deployments` | DeploymentTarget[] | Yes | One or more deployment targets |
| `build` | object | No | Build-specific options (esbuild config, dependencies) |
| `transport` | TransportConfig | No | Per-protocol defaults consumed by `dev` / `inspector` / `pm` (issue #400) |
| `env` | EnvOverlays | No | `shared` ⊕ `dev` / `test` / `ship` overlays merged into the spawned child env (issue #400) |
| `clients` | ClientsConfig | No | Per-client connection snippets emitted by `frontmcp eject-mcp-config <client>` (issue #400) |
| `test` | TestConfig | No | `frontmcp test` defaults overridden by CLI flags (issue #400) |
| `skills` | SkillsCliConfig | No | `frontmcp skills install` / `export` defaults (issue #400) |

### Per-command consumption (issue #400)

| Command | Config fields consumed |
| --- | --- |
| `build` | `name`, `version`, `entry`, `deployments`, `build`, `nodeVersion` |
| `dev` | `entry`, `transport.http.port`, `env.shared` ⊕ `env.dev` |
| `test` | `test.timeoutMs`, `test.runInBand`, `test.coverage`, `test.testMatch`, `env.shared` ⊕ `env.test` |
| `inspector` | `transport.default`, `transport.http.port`, `transport.stdio.command/args` |
| `pm start` / `socket` / `service` | `name`, `entry`, `transport.http.port`, `transport.http.socketPath`, `env.shared` ⊕ `env.ship` |
| `skills install` / `export` | `skills.provider`, `skills.bundle`, `skills.install`, `skills.exportTarget` |
| `eject-mcp-config <client>` | `clients.<client>`, `name`, `transport`, `env.ship` |

## Transport defaults

```ts
transport: {
  default: 'http',                     // 'http' | 'sse' | 'stdio'
  http: { port: 3000, path: '/mcp', host: '127.0.0.1' },
  stdio: { command: 'node', args: ['dist/main.js'] },
}
```

## Env overlays

`shared` applies everywhere; mode overlays (`dev`, `test`, `ship`) layer on top:

```ts
env: {
  shared: { LOG_LEVEL: 'info' },
  dev:    { NODE_ENV: 'development' },
  test:   { NODE_ENV: 'test', JEST_WORKER_ID: '1' },
  ship:   { NODE_ENV: 'production' },
}
```

Note: `.env` and `.env.local` (loaded by `dev`) still win over config overlays — file-based env is the deployment escape hatch.

## Client snippets

```ts
clients: {
  'claude-code':    { name: 'my-server', transport: 'http', url: 'http://127.0.0.1:3000/mcp' },
  'claude-desktop': { transport: 'stdio', command: 'npx', args: ['-y', 'my-server'] },
  cursor:           { transport: 'http', url: 'http://127.0.0.1:3000/mcp' },
  windsurf:         { transport: 'stdio', command: 'npx', args: ['-y', 'my-server'] },
  vscode:           { transport: 'stdio', command: 'npx', args: ['-y', 'my-server'] },
}
```

Emit a ready-to-paste snippet:

```bash
frontmcp eject-mcp-config claude-code              # prints to stdout
frontmcp eject-mcp-config claude-code --out ~/.config/claude/mcp.json
frontmcp eject-mcp-config claude-code --out ~/.config/claude/mcp.json --dry-run
```

## Deployment Targets

Each entry in `deployments` defines a build target with independent settings:

| Target | Description | Transport | Storage |
| --- | --- | --- | --- |
| `node` | Standalone Node.js server | Streamable HTTP, SSE, stdio | Redis, SQLite, memory |
| `distributed` | Multi-pod with HA | Streamable HTTP | Redis (required) |
| `vercel` | Vercel Functions | Streamable HTTP | Vercel KV |
| `lambda` | AWS Lambda | Streamable HTTP | DynamoDB, ElastiCache |
| `cloudflare` | Cloudflare Workers | Streamable HTTP | KV, Durable Objects |
| `browser` | Browser bundle | In-memory | Memory only |
| `cli` | Standalone binary | stdio | SQLite, memory |
| `sdk` | Library for embedding | Direct (in-process) | Configurable |
| `mcpb` | MCP Bundle archive (see [MCPB](/frontmcp/deployment/mcpb)) | stdio | SQLite, memory |

### Deployment Target Fields

| Field | Type | Description |
| --- | --- | --- |
| `target` | string | One of the 9 target types above |
| `server` | ServerConfig | HTTP, CORS, CSP, and security header settings |
| `ha` | HaConfig | HA settings (distributed target only) |
| `cli` | CliConfig | CLI-specific settings (cli target only) |
| `js` | boolean | Generate .js bundle instead of native binary (cli target) |
| `env` | Record | Per-target environment variables |

### MCPB-Only Fields

The `mcpb` target supports additional fields for [MCP Bundle](/frontmcp/deployment/mcpb) metadata:

| Field | Type | Description |
| --- | --- | --- |
| `displayName` | string | Human-friendly bundle title shown in the installer |
| `longDescription` | string | Markdown description for the extension details page |
| `author` | `{ name, email?, url? }` | Overrides parsed `package.json.author` |
| `license` | string | SPDX license identifier |
| `homepage` | string | Project homepage URL |
| `repository` | string \| `{ type, url }` | Source repository |
| `documentation` | string | Documentation URL |
| `support` | string | Support / issues URL |
| `icon` | string | Icon path relative to project root (PNG) |
| `keywords` | string[] | Keywords for client search |
| `privacyPolicies` | string[] | Privacy policy URLs for external services this server talks to |
| `compatibility` | object | `{ claude_desktop?, platforms?, runtimes? }` constraints |
| `userConfig` | Record | MCPB `user_config` overrides (type, min/max, etc.) — merged with translated `setup.steps` |
| `sea` | `{ enabled?, mergeFrom? }` | Build SEA binary for the host and/or merge pre-built binaries |
| `includeNodeModules` | boolean | Include `server/node_modules/` in the archive (off by default) |
| `deterministic` | boolean | Produce byte-identical archives across runs (defaults to `true`) |

## Server Configuration

```typescript
export default defineConfig({
  name: 'my-server',
  deployments: [{
    target: 'node',
    server: {
      http: {
        port: 3000,
        socketPath: '/tmp/mcp.sock',  // Unix socket (alternative to port)
        entryPath: '/mcp',
        cors: {
          origins: ['https://app.example.com'],
          credentials: true,
          maxAge: 600,
        },
      },
      csp: {
        enabled: true,
        directives: {
          'default-src': "'self'",
          'script-src': "'self' https://cdn.example.com",
        },
        reportUri: 'https://report.example.com/csp',
        reportOnly: false,
      },
      headers: {
        hsts: 'max-age=31536000; includeSubDomains',
        contentTypeOptions: 'nosniff',
        frameOptions: 'DENY',
      },
      cookies: {
        affinity: '__frontmcp_node',
        domain: 'example.com',
        sameSite: 'Lax',
      },
    },
  }],
});
```

### HTTP Options

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `port` | number | 3000 | HTTP listen port (node / distributed only) |
| `socketPath` | string | --- | Unix socket path (alternative to port; node / distributed only) |
| `entryPath` | string | `''` | Base path for the MCP endpoint (e.g. `'/mcp'`) |
| `cors` | object | --- | CORS configuration — `{ origins?, credentials?, maxAge? }` |
| `bodyLimit` | number \| string | `'4mb'` | Max body size for `express.json()`. Bytes (number) or body-parser string (`'4mb'`, `'500kb'`, …). Oversized requests get HTTP 413 with a JSON-RPC envelope. See [Transport Security → Request Body Limits](/frontmcp/deployment/transport-security#request-body-limits). |
| `urlencodedLimit` | number \| string | falls back to `bodyLimit` | Max body size for `application/x-www-form-urlencoded`. |

### CSP Options

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `enabled` | boolean | false | Enable CSP headers |
| `directives` | Record | --- | Directive name to value(s) map (e.g., `{ 'default-src': "'self'" }`) |
| `reportUri` | string | --- | URI for CSP violation reports |
| `reportOnly` | boolean | false | Use `Content-Security-Policy-Report-Only` header |

### Security Headers

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `hsts` | string | --- | `Strict-Transport-Security` value |
| `contentTypeOptions` | string | `nosniff` | `X-Content-Type-Options` value |
| `frameOptions` | string | `DENY` | `X-Frame-Options` value |

### HA Configuration

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `heartbeatIntervalMs` | number | 10000 | Heartbeat write interval |
| `heartbeatTtlMs` | number | 30000 | Heartbeat TTL (2-3x interval) |
| `takeoverGracePeriodMs` | number | 5000 | Grace period before takeover |
| `redisKeyPrefix` | string | `mcp:ha:` | Redis key prefix |

## Multi-Target Example

Deploy the same server to Node.js, distributed, and Vercel:

```typescript
import { defineConfig } from 'frontmcp';

export default defineConfig({
  name: 'my-server',
  version: '1.0.0',
  deployments: [
    {
      target: 'node',
      server: { http: { port: 3000 } },
    },
    {
      target: 'distributed',
      ha: { heartbeatIntervalMs: 5000 },
      server: {
        csp: {
          enabled: true,
          directives: { 'default-src': "'self'", 'upgrade-insecure-requests': '' },
        },
        headers: { hsts: 'max-age=31536000; includeSubDomains' },
      },
    },
    {
      target: 'vercel',
    },
  ],
});
```

Build each target independently:

```bash
frontmcp build --target node
frontmcp build --target distributed
frontmcp build --target vercel
```

## Project-Defined CLI Commands

The optional `cli.commands` block registers project-specific verbs that
ship alongside the built-in `frontmcp` commands. Each verb spawns a
runner module from the project (TS or JS) as a child process, so the
project's own code stays out of the CLI's process.

Verb names must match `/^[a-zA-Z][a-zA-Z0-9:_-]*$/` — start with a letter,
then letters, digits, `:`, `_`, or `-`. Examples: `deploy`, `db-migrate`,
`project:init`. Names that match a built-in verb (see [Reserved Verbs](#reserved-verbs)
below) are rejected at config load.

Each command entry accepts:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `entry` | string | yes | Path to the runner module (TS or JS). |
| `description` | string | no | Shown in `frontmcp --help` and `--list-commands`. |
| `arguments` | array | no | Positional argument definitions (variadic must be last). |
| `options` | array | no | Long/short flag definitions with optional defaults. |
| `hidden` | boolean | no | When `true`, the verb still runs but is omitted from help output and `--list-commands`. Useful for internal scripts you don't want surfaced to end users. |

```typescript
// frontmcp.config.ts
import { defineConfig } from 'frontmcp';

export default defineConfig({
  name: 'my-server',
  deployments: [{ target: 'node' }],
  cli: {
    commands: {
      deploy: {
        entry: './scripts/deploy.ts',
        description: 'Push the current build to staging',
        arguments: [{ name: 'env', required: true, description: 'Target env' }],
        options: [
          { flags: '-n, --dry-run', description: 'Skip writes' },
          { flags: '-c, --concurrency <num>', default: 4 },
        ],
      },
      'db-migrate': {
        entry: './scripts/migrate.ts',
        description: 'Run pending database migrations',
      },
      'internal:reindex': {
        entry: './scripts/reindex.ts',
        description: 'Rebuild local search index',
        hidden: true, // omitted from --help and --list-commands
      },
    },
  },
});
```

After saving the config, the new verbs show up under **Project Commands**
in `frontmcp --help`:

```bash
frontmcp deploy prod --concurrency 8
frontmcp db-migrate
frontmcp --list-commands   # Lists every verb with [built-in]/[project] tag
```

### Reserved Verbs

Verb names that collide with a built-in command (`dev`, `build`, `test`,
`start`, `skills`, etc.) are rejected at config-load time with a helpful
message. Use a project-namespaced prefix (e.g. `project:init`,
`db-migrate`) to avoid the collision.

### Runner Selection

| Entry extension | Runner |
| --------------- | ------ |
| `.ts`, `.tsx`, `.mts`, `.cts` | `node --import tsx <entry>` |
| `.js`, `.mjs`, `.cjs`         | `node <entry>` |

The runner receives the parsed positionals as argv plus a
`FRONTMCP_PROJECT_COMMAND` env var containing a JSON payload with
`verb`, `positionals`, `options`, and `cwd` for richer scripting.

## Helper Functions

```typescript
import { defineConfig, loadFrontMcpConfig, findDeployment, getDeploymentTargets } from 'frontmcp';

// Load config from a directory
const config = await loadFrontMcpConfig(process.cwd());

// Find a specific deployment target
const distributed = findDeployment(config, 'distributed');

// List all configured targets
const targets = getDeploymentTargets(config); // ['node', 'distributed', 'vercel']
```

## Related

<CardGroup cols={2}>
  <Card title="Production Build" icon="rocket" href="/frontmcp/deployment/production-build">
    Build and deploy guide
  </Card>
  <Card title="High Availability" icon="server" href="/frontmcp/deployment/high-availability">
    Multi-pod deployment with session failover
  </Card>
  <Card title="Security Headers" icon="shield-halved" href="/frontmcp/deployment/security-headers">
    CSP and security header configuration
  </Card>
  <Card title="Runtime Modes" icon="layer-group" href="/frontmcp/deployment/runtime-modes">
    Standalone, distributed, and serverless modes
  </Card>
</CardGroup>
