Skip to content

Configuration

MCP Front uses a single JSON config file.

{
"version": "v0.0.1-DEV_EDITION_EXPECT_CHANGES",
"proxy": {
"baseURL": "https://mcp.company.com",
"addr": ":8080",
"auth": { ... }
},
"mcpServers": { ... }
}

The version string signals the config format may change in future releases.

mcp-front -config config.json # Run with config file (required)
mcp-front -config-init config.json # Generate a default config file
mcp-front -config config.json -validate # Validate config and exit
mcp-front -version # Print version
mcp-front -help # Print usage

The -validate flag checks your config for errors without starting the server. It validates structure, field types, and references without requiring environment variables to be set.

Any string value in the config can reference environment variables using {"$env": "VAR_NAME"}. This keeps secrets out of your config files. Values are resolved at config load time.

{
"jwtSecret": { "$env": "JWT_SECRET" },
"url": { "$env": "DATABASE_MCP_URL" }
}

This syntax prevents accidental shell expansion when configs pass through scripts or CI/CD pipelines.

For MCP servers that need per-user tokens injected at request time, use the {"$userToken": "...{{token}}..."} syntax. The {{token}} placeholder is replaced with the authenticated user’s service token when a request is made.

{
"env": {
"API_KEY": { "$userToken": "{{token}}" },
"AUTH_HEADER": { "$userToken": "Bearer {{token}}" }
}
}

This works in env, args, url, and headers fields on MCP servers. See Service Authentication for how users provide these tokens.

Your public URL. Required. Must be HTTPS in production.

Listen address, typically ":8080" for port 8080 on all interfaces.

Used as the MCP server implementation name. Optional.

Session management configuration. All fields are optional.

{
"proxy": {
"sessions": {
"timeout": "24h",
"cleanupInterval": "15m",
"maxPerUser": 5
}
}
}

timeout controls how long sessions last (default: "5m"). cleanupInterval is how often expired sessions are garbage collected (default: "1m"). maxPerUser limits concurrent sessions per user (default: 10, set to 0 for unlimited).

Configure OAuth under proxy.auth.

{
"auth": {
"kind": "oauth",
"issuer": "https://mcp.company.com",
"idp": {
"provider": "google",
"clientId": { "$env": "GOOGLE_CLIENT_ID" },
"clientSecret": { "$env": "GOOGLE_CLIENT_SECRET" },
"redirectUri": "https://mcp.company.com/oauth/callback"
},
"allowedDomains": ["company.com"],
"allowedOrigins": ["https://claude.ai"],
"allowedRedirectUriHosts": ["https://claude.ai"],
"tokenTtl": "4h",
"storage": "memory",
"jwtSecret": { "$env": "JWT_SECRET" },
"encryptionKey": { "$env": "ENCRYPTION_KEY" }
}
}

Must be "oauth". This is the only supported authentication kind.

The OAuth issuer URI. Should match your baseURL.

Identity provider configuration. MCP Front supports Google, Azure AD, GitHub, and generic OIDC providers. See Identity Providers for setup details.

Restricts access to users with email addresses from these domains. At least one of allowedDomains or idp.allowedOrgs is required.

CORS origin whitelist. When empty, all origins are allowed. Set to ["https://claude.ai"] to restrict to Claude, or add additional origins for other MCP clients.

Allowlist of permitted redirect-URI origins for OAuth client registration and authorization. Each entry is an origin in the form scheme://host[:port] (for example "https://claude.ai" or "http://127.0.0.1" for native MCP clients per RFC 8252). A registered redirect URI passes when its scheme and hostname match an entry, case-insensitively per RFC 3986. An entry without a port matches any port for that host; an entry with an explicit port matches only that port.

This gates Dynamic Client Registration: an MCP client trying to register a redirect_uris value whose host is not on this list is rejected with 400 Bad Request at /register. The same check is re-applied at /authorize to catch clients registered before the allowlist was tightened. Structural checks always apply regardless of this list — a redirect URI must be an absolute URI, must have a host, must not be opaque (which catches the common form of javascript: and data: URIs), and must not contain a fragment.

In production (i.e. when MCP_FRONT_ENV is not development), one of allowedRedirectUriHosts or allowAnyRedirectUriHost is required at startup. The server fails to boot if both are absent.

Set to true to explicitly disable the host allowlist. Structural checks still apply. Cannot be combined with a non-empty allowedRedirectUriHosts. Intended for local development and trusted-tenant deployments where the operator has other controls in place; production deployments should prefer the explicit allowlist.

Access token lifetime as a Go duration string. Default: "1h". Examples: "4h", "30m", "24h".

Refresh token lifetime. Default: "720h" (30 days).

When set, refresh tokens are only issued if the authorization request includes at least one of these scopes. Default: empty (always issue refresh tokens).

"memory" (default) for development — data lost on restart. "firestore" for production with persistent storage.

Secret for signing JWT tokens. Must be at least 32 bytes. Must use {"$env": "VAR"} syntax.

Secret for encrypting sensitive data at rest (AES-256-GCM). Must be exactly 32 bytes. Required for Firestore storage, optional for memory storage. Must use {"$env": "VAR"} syntax.

When storage is "firestore":

{
"auth": {
"storage": "firestore",
"gcpProject": { "$env": "GOOGLE_CLOUD_PROJECT" },
"firestoreDatabase": "(default)",
"firestoreCollection": "mcp_front_data"
}
}

gcpProject is your GCP project ID (required for Firestore). firestoreDatabase defaults to "(default)". firestoreCollection defaults to "mcp_front_data".

When true, allows tokens with just the base issuer as audience to access any service. This is a workaround for MCP clients that don’t implement RFC 8707 resource indicators, but it defeats per-service token isolation. Default: false. Only enable if you understand the security implications.

Each server needs at least a transportType. See Server Types for transport-specific documentation.

{
"mcpServers": {
"postgres": {
"transportType": "stdio",
"command": "docker",
"args": ["run", "--rm", "-i", "my-postgres-mcp"],
"env": {
"DATABASE_URL": { "$env": "DATABASE_URL" }
}
},
"linear": {
"transportType": "sse",
"url": "http://linear-mcp:3000/sse"
}
}
}

Server names must start with an alphanumeric character and contain only alphanumeric characters, underscores, and hyphens. A server named "postgres" is accessible at /postgres/sse.

Per-server authentication validated on incoming requests before proxying. Useful for development or non-OAuth MCP clients.

Bearer tokens:

{
"serviceAuths": [
{
"type": "bearer",
"name": "dev",
"tokens": ["dev-token-123", "dev-token-456"]
}
]
}

The name field is the identity used in logs, session keys, and per-user limits. Required for bearer; optional for basic (defaults to username). Names must be unique within a server’s serviceAuths across types.

Basic authentication:

{
"serviceAuths": [
{
"type": "basic",
"username": "admin",
"password": { "$env": "ADMIN_PASSWORD" }
}
]
}

Tokens for one server don’t work for another — each server’s serviceAuths are independent.

When a backend service needs individual user tokens (Notion API keys, Linear OAuth tokens), set requiresUserToken: true and configure userAuthentication. See Service Authentication for details.

Filter which tools are exposed to clients.

{
"options": {
"toolFilter": {
"mode": "allow",
"list": ["safe_tool_1", "safe_tool_2"]
}
}
}

mode is "allow" (only expose listed tools) or "block" (hide listed tools).

Set type to "aggregate" to combine tools from multiple backends into one endpoint. See Server Types for details.

{
"all": {
"type": "aggregate",
"servers": ["postgres", "linear"],
"discovery": {
"timeout": "10s",
"cacheTtl": "5m",
"maxConnsPerUser": 10
}
}
}

Optional variables that control runtime behavior:

Terminal window
MCP_FRONT_ENV=development # Relaxes OAuth validation for local dev (allows HTTP, makes allowedRedirectUriHosts optional)
LOG_LEVEL=debug # Options: trace, debug, info, warn, error
LOG_FORMAT=json # Options: json (structured) or text (human-readable)
{
"version": "v0.0.1-DEV_EDITION_EXPECT_CHANGES",
"proxy": {
"name": "Production Proxy",
"baseURL": "https://mcp.company.com",
"addr": ":8080",
"auth": {
"kind": "oauth",
"issuer": "https://mcp.company.com",
"idp": {
"provider": "google",
"clientId": { "$env": "GOOGLE_CLIENT_ID" },
"clientSecret": { "$env": "GOOGLE_CLIENT_SECRET" },
"redirectUri": "https://mcp.company.com/oauth/callback"
},
"allowedDomains": ["company.com"],
"allowedOrigins": ["https://claude.ai"],
"allowedRedirectUriHosts": ["https://claude.ai"],
"tokenTtl": "4h",
"storage": "firestore",
"gcpProject": { "$env": "GOOGLE_CLOUD_PROJECT" },
"firestoreDatabase": "(default)",
"firestoreCollection": "mcp_front_data",
"jwtSecret": { "$env": "JWT_SECRET" },
"encryptionKey": { "$env": "ENCRYPTION_KEY" }
}
},
"mcpServers": {
"database": {
"transportType": "sse",
"url": { "$env": "DATABASE_MCP_URL" }
},
"notion": {
"transportType": "stdio",
"command": "notion-mcp",
"requiresUserToken": true,
"userAuthentication": {
"type": "manual",
"displayName": "Notion Integration Token",
"instructions": "Create a new internal integration and copy the secret.",
"helpUrl": "https://www.notion.so/my-integrations",
"validation": "^secret_[a-zA-Z0-9]{43}$"
},
"env": {
"NOTION_TOKEN": { "$userToken": "{{token}}" }
}
}
}
}

This example adds an aggregate server to the production config. OAuth configuration is omitted for brevity — see the production example above.

{
"mcpServers": {
"postgres": {
"transportType": "sse",
"url": "http://postgres-mcp:3000/sse"
},
"linear": {
"transportType": "stdio",
"command": "linear-mcp",
"args": ["serve"]
},
"all": {
"type": "aggregate",
"discovery": {
"cacheTtl": "5m"
}
}
}
}

Claude connects to /all/sse and sees namespaced tools like postgres.query and linear.create_issue.