Config file reference
Location
Default: ~/.config/mcp/servers.json
Override: MCP_CONFIG_PATH environment variable.
Schema
{
"mcpServers": {
"<name>": <ServerConfig>,
...
}
}ServerConfig
Three variants, distinguished by their fields:
Stdio server
{
"command": "npx",
"args": ["-y", "package-name"],
"env": {
"KEY": "value"
}
}command
string
required
Executable to spawn
args
string[]
[]
Arguments passed to the command
env
object
{}
Environment variables for the process
min_idle_timeout
string
"1m"
Minimum idle timeout for adaptive mode
max_idle_timeout
string
"5m"
Maximum idle timeout for adaptive mode
HTTP server
url
string
required
Server endpoint URL
headers
object
{}
HTTP headers for every request
min_idle_timeout
string
"1m"
Minimum idle timeout for adaptive mode
max_idle_timeout
string
"5m"
Maximum idle timeout for adaptive mode
CLI server
command
string
required
CLI executable to wrap
cli
bool
required
Must be true — marks this as a CLI server
cli_help
string
"--help"
Flag used to discover subcommands and options
cli_depth
number
2
How deep to recurse into subcommands for flag discovery
cli_only
string[]
[] (all)
Whitelist of subcommands to expose
args
string[]
[]
Base arguments prepended to every invocation
env
object
{}
Environment variables for the CLI process
tools
array
[]
Preset tool definitions (skips auto-discovery when set)
min_idle_timeout
string
"1m"
Minimum idle timeout for adaptive mode
max_idle_timeout
string
"5m"
Maximum idle timeout for adaptive mode
See the CLI as MCP guide for discovery details and examples.
Idle timeout
Controls when the proxy shuts down idle backend connections to reclaim resources. Applies to both stdio and HTTP backends in proxy mode (mcp serve).
Policy values
"adaptive" (default)
Timeout adjusts based on usage frequency — frequently used backends stay alive longer
"never"
Never shut down — backend stays connected for the entire proxy lifetime
"<duration>"
Fixed timeout (e.g. "3m", "30s", "1h")
Duration format: number followed by s (seconds), m (minutes), or h (hours). Plain numbers are treated as seconds.
Adaptive mode
When idle_timeout is "adaptive" (the default), the proxy tracks how often each backend is used and assigns a timeout tier:
Hot
> 20
5 min
Warm
5–20
3 min
Cold
< 5
1 min
The tier is computed from the backend's total request count divided by its uptime. The result is clamped between min_idle_timeout (default 1m) and max_idle_timeout (default 5m).
When a backend is shut down due to inactivity, its tools remain visible in tools/list. On the next tools/call, the proxy reconnects automatically (lazy initialization). Usage history is preserved across reconnections so the adaptive algorithm has continuity.
Examples
Tool ACL overrides
The proxy ships with an automatic classifier that labels every tool of every upstream MCP as read, write, or ambiguous (treated as write, fail-safe). The classifier is auditable — run mcp acl classify to see the verdict, confidence, source, and reasons for each tool.
When the classifier is wrong (or when you just want to be explicit), add tool_acl to any server and pin individual tools to read or write using the same glob syntax as the ACL rules (*, prefix, suffix, contains).
Semantics:
Both
readandwriteare optional — omit either or both.Overrides run before the classifier. A tool that matches an override is never scored.
The same pattern string may not appear in both
readandwritefor the same server — that fails loudly at load time.If two different globs on the same server both match a single tool name (e.g.
get_*inreadand*_thinginwriteboth matchget_thing), the proxy fails safe towriteat classification time. Narrow your globs to avoid this.Overrides are never cached — they are re-read from the config on every startup.
For the full redesign plan and the token/description dictionaries the classifier uses, see docs/acl-redesign-plan.md.
Type detection
The config uses serde's untagged enum deserialization. The type is inferred from the fields:
Has
command+cli: true→ CLIHas
command(withoutcli) → StdioHas
url→ HTTP
CLI is checked first, then Stdio, then HTTP.
Environment variable substitution
Any ${VAR_NAME} in a string value is replaced with the env var's value at load time.
Missing env vars resolve to empty string "".
Reserved names
These names cannot be used as server names:
searchaddremovelisthelpversion
Using a reserved name won't break the config, but you'll get a warning and the server may be shadowed by built-in commands.
Server authentication (serverAuth)
serverAuth)Optional. Configures authentication for mcp serve --http. Ignored for direct CLI usage.
Providers
providers is an array of provider names, evaluated in order as a chain. The first provider that accepts the request wins; if all reject, the chain returns the error of the first provider configured (oracle-resistant). Empty array (or omitted) is equivalent to anonymous access.
"none"
Anonymous identity (subject: "anonymous", no roles). Useful for testing.
"bearer"
Static bearer token validation. Reads from bearer sub-config.
"forwarded"
Trust reverse proxy header (e.g. X-Forwarded-User). Reads from forwarded sub-config.
"oauth_as"
OAuth 2.0 Authorization Server with Dynamic Client Registration — the route required by Claude.ai / ChatGPT / Cursor. Reads from oauthAs sub-config. See the OAuth AS how-to.
Schema change. Earlier versions used a single
provider: "..."string. The new schema is the arrayproviders: [...]. Configs carrying the legacy field deserialize into an empty providers list and boot asNoAuth— silently weakening auth, so an explicit migration is required. Booting with a missing sub-config (e.g."bearer"listed without abearerblock) fails at startup rather than degrading.
Bearer config
Required when "bearer" is in providers. Each entry in tokens accepts two shapes:
Legacy (string):
"<token>": "<subject>"— subject only, no roles.Extended (object):
"<token>": { "subject": "<subject>", "roles": ["<role>", ...] }— subject plus roles used by ACL evaluation.
Both forms can coexist in the same file.
tokens
object
Map of token → subject string or {subject, roles} object
Each extended entry:
subject
string
required
User identity for this token
roles
string[]
[]
Roles assigned to this identity (used by ACL rules)
Forwarded config
Optional when "forwarded" is in providers. Reads the authenticated user from a header set by a trusted reverse proxy, and optionally reads a groups header to populate roles.
header
string
"x-forwarded-user"
Header name to read the authenticated user from
groups_header
string
"x-forwarded-groups"
Header name to read roles from (comma-separated, oauth2-proxy convention)
Groups header value is parsed as a comma-separated list: each entry is trimmed and empty entries are dropped. Missing header yields empty roles (not an error). Role matching is case-sensitive.
Only use
forwardedbehind a trusted reverse proxy. The proxy must strip these headers from incoming client requests — otherwise a client could forge identity and roles.
OAuth AS config (oauthAs)
oauthAs)Required when "oauth_as" is in providers. Turns mcp serve into an OAuth 2.0 Authorization Server with Dynamic Client Registration so Claude.ai, ChatGPT, Cursor and other AI clients can connect. User authentication is delegated to a trusted reverse proxy (oauth2-proxy / Cloudflare Access / Pomerium) — mcp serve never handles passwords.
issuerUrl
string
yes
—
Public origin the AS advertises in metadata and embeds as iss in JWTs. Must match what clients reach.
jwtSecret
string
yes
—
HMAC-SHA256 signing key. Must be ≥ 32 bytes — boot fails otherwise.
trustedUserHeader
string
no
"x-forwarded-user"
Header read at /authorize to identify the human.
trustedGroupsHeader
string
no
"x-forwarded-groups"
Comma-separated → JWT groups claim.
trustedSourceCidrs
string[]
yes
—
CIDRs allowed to reach /authorize. Empty list rejected at boot — without it any client could spoof the trusted user header.
accessTokenTtlSeconds
number
no
3600
JWT lifetime.
refreshTokenTtlSeconds
number
no
2592000 (30d)
Refresh-token lifetime.
authorizationCodeTtlSeconds
number
no
60
Authorization-code lifetime.
scopesSupported
string[]
no
[]
Scopes advertised in metadata.
redirectUriAllowlist
string[]
yes
—
Patterns clients may register. Trailing * allowed (for ChatGPT-style URIs).
injectedRoles
string[]
no
[]
Roles always added to issued JWTs — useful as a "came in via OAuth" marker for ACL discrimination.
State (registered clients via DCR + refresh tokens) persists to auth_server.json in the config directory; override with MCP_AUTH_SERVER_PATH or inline via MCP_AUTH_SERVER_CONFIG. See the OAuth AS how-to for setup, security notes, and troubleshooting.
ACL config
Optional. Controls which users can access which tools. Supports two schemas: role-based (recommended) and legacy (backward compatible). Detection is automatic — see schema detection.
Role-based schema (recommended)
default
"allow" | "deny"
"allow"
Policy when no grant matches
strictClassification
bool
false
Block ambiguous tools entirely (require explicit override)
roles
object
{}
Map of role name → list of grants
subjects
object
{}
Map of subject → { roles, extra }
Grant
server
string or string[]
required
Server alias(es) to match ("*" = any)
access
"read" | "write" | "*"
required
Access level
tools
string[]
[] (all tools)
Tool name globs to narrow the grant
resources
string[]
[] (all resources)
Resource URI globs to narrow the grant. Same * syntax as tools.
prompts
string[]
[] (all prompts)
Prompt name globs to narrow the grant. Same * syntax as tools.
deny
bool
false
Turns grant into explicit deny (always wins over allows)
Subject config
roles
string[]
[]
Roles assigned to this subject (merged with token roles)
extra
Grant[]
[]
Additional per-subject grants
Access expansion
access
Read tools
Write tools
Ambiguous tools
Ambiguous (strict)
"read"
allowed
denied
denied
denied
"write"
denied
allowed
allowed
denied
"*"
allowed
allowed
allowed
denied
Evaluation model
Collect all grants from all roles (token roles + subject config roles) +
extraFilter to grants matching the target server and tool
If any matching grant has
deny: true→ denyIf any matching allow grant covers the access level → allow
No match → apply
default
Union-based, order-independent. Deny always wins.
Legacy schema
default
"allow" | "deny"
"allow"
Default policy when no rule matches
rules
array
[]
Ordered list of ACL rules (first match wins)
Legacy ACL rule
subjects
string[]
[] (match all)
User subjects to match (* = any)
roles
string[]
[] (match all)
Roles to match (* = any)
tools
string[]
required
Tool name patterns (supports * wildcards — prefix, suffix, middle, multiple)
policy
"allow" | "deny"
required
Action when rule matches
Both subjects and roles must match for a rule to apply. Empty means "match all".
Schema detection
roles (as object) or subjects (as object)
Role-based
rules (as array)
Legacy
Both rules and roles/subjects
Config error
Neither
Legacy with default allow
Tool pattern glob syntax
The tools field supports glob patterns with * wildcards (both schemas):
sentry__*
Anything starting with sentry__
sentry__search_issues
*_issues
Anything ending with _issues
search_issues, sentry__list_issues
*admin*
Anything containing admin
admin_panel, user_admin_tools
sentry__*_admin__*
Multiple wildcards
sentry__team_admin__delete
my_tool
Exact match (no wildcards)
my_tool
*
Everything
any tool
Auth store
Tokens and OAuth client registrations are stored separately in:
Keys are normalized server URLs (trailing slash removed).
Last updated
Was this helpful?