docs/configuration.md
Complete reference for MCPProxy configuration file (mcp_config.json). This document covers all configuration options, their defaults, and usage examples.
MCPProxy looks for configuration in these locations (in order):
| OS | Config Location |
|---|---|
| macOS | ~/.mcpproxy/mcp_config.json |
| Windows | %USERPROFILE%\.mcpproxy\mcp_config.json |
| Linux | ~/.mcpproxy/mcp_config.json |
Note: At first launch, MCPProxy automatically generates a minimal configuration file if none exists.
{
"listen": "127.0.0.1:8080"
}
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | "127.0.0.1:8080" | Network address to bind to. Use :8080 for all interfaces, 127.0.0.1:8080 for localhost only (recommended for security) |
Examples:
"127.0.0.1:8080" - Localhost only (default, secure)":8080" - All network interfaces (use with caution)"0.0.0.0:9000" - All interfaces on port 9000{
"data_dir": "~/.mcpproxy"
}
| Field | Type | Default | Description |
|---|---|---|---|
data_dir | string | "~/.mcpproxy" | Directory for database and certificates. Supports ~ expansion for home directory. Logs use OS log directories unless log_dir is set |
{
"enable_socket": true,
"tray_endpoint": ""
}
| Field | Type | Default | Description |
|---|---|---|---|
enable_socket | boolean | true | Enable Unix socket (macOS/Linux) or named pipe (Windows) for secure local IPC between tray and core |
tray_endpoint | string | "" | Override socket/pipe path (advanced, usually not needed) |
{
"tools_limit": 15,
"tool_response_limit": 20000,
"call_tool_timeout": "2m",
"init_timeout": "30s"
}
| Field | Type | Default | Description |
|---|---|---|---|
tools_limit | integer | 15 | Maximum number of tools to return per request (1-1000) |
tool_response_limit | integer | 20000 | Maximum characters in tool responses (0 = unlimited) |
call_tool_timeout | string | "2m" | Timeout for tool calls (e.g., "30s", "2m", "5m"). Note: When using agents like Codex or Claude as MCP servers, you may need to increase this timeout significantly, even up to 10 minutes ("10m"), as these agents may require longer processing times for complex operations |
init_timeout | duration | "30s" | Deadline for an upstream's MCP initialize handshake (e.g. "30s", "120s", "3m"). Raise this for servers that do legitimate first-run warmup — building a cache/index or prefetching — before they answer initialize, so they are not killed mid-startup. Global default; can be overridden per server (see Server Fields). Range: 1s–30m; "0s"/unset uses the 30s default. |
mcpproxy keeps each upstream connection alive and its tool index fresh with two background loops. Both intervals are tunable globally and per server, so you can quiet a chatty upstream that returns a large tool catalog.
{
"health_check_interval": "30s",
"tool_discovery_interval": "5m"
}
| Field | Type | Default | Description |
|---|---|---|---|
health_check_interval | duration | "30s" | How often to probe each connected server for liveness with a lightweight MCP ping. "0s" disables the periodic probe. Range: 5s–1h. Does not apply to Docker-isolated servers (see note below). |
tool_discovery_interval | duration | "5m" | How often to re-list every server's tools to rebuild the search index. "0s" disables the periodic sweep. Range: 30s–24h. Applies to all server types, including Docker. |
Docker-isolated servers.
health_check_intervalhas no effect on Docker-isolated servers. Their liveness is monitored separately at the container level on a fixed internal cadence (not an MCPping), so the periodic ping probe is intentionally skipped for them.tool_discovery_intervalstill applies to Docker servers. Remote (HTTP/SSE) servers benefit most from thepingswitch, since both the probe and the formertools/listcrossed the network.
Liveness uses ping, not tools/list. The health-check loop issues the
MCP-standard ping request rather than re-listing every tool, so an idle proxy
no longer generates large recurring tools/list traffic to upstream servers
(GitHub #608). Tool
changes are still picked up reactively whenever a server pushes
notifications/tools/list_changed.
Disabling a loop ("0s"). Set either key to "0s" to turn the
corresponding loop off:
health_check_interval: "0s" — no periodic liveness probe. A dead transport
is then detected lazily, on the next real tool call or discovery sweep, rather
than proactively.tool_discovery_interval: "0s" — no periodic index rebuild. Tools are still
discovered at connect time and whenever a server pushes
notifications/tools/list_changed. Trade-off: a server that does not
support list_changed will not have new/removed tools reflected until it
reconnects or you trigger a manual refresh.An unset key behaves exactly as before this feature (the built-in default), and a change to either interval takes effect on the next cycle without restarting the proxy.
Per-server override. Both keys can also be set on an individual server entry under
mcpServers[](see Server Fields) to override the global value for just that server; the per-server value wins, and"0s"disables the loop for that server only. A dedicated per-server form control in the Web UI / macOS app is planned; for now set per-server overrides via the Raw JSON editor or the REST API.
{
"debug_search": false,
"enable_prompts": true,
"check_server_repo": true
}
| Field | Type | Default | Description |
|---|---|---|---|
debug_search | boolean | false | Enable debug logging for search operations |
enable_prompts | boolean | true | Enable MCP prompts feature for workflow guidance and interactive assistance with common tasks (finding tools, debugging search, setting up servers, troubleshooting connections) |
check_server_repo | boolean | true | Enable repository detection for MCP servers (shows install commands) |
{
"mcpServers": [
{
"name": "my-server",
"protocol": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"working_dir": "/path/to/project",
"env": {
"API_KEY": "secret-value"
},
"enabled": true,
"quarantined": false
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique server identifier |
protocol | string | No | Transport protocol: stdio, http, sse, streamable-http, or auto (default: inferred from command/url) |
command | string | Yes* | Command to execute (required for stdio protocol) |
args | array | No | Command arguments |
url | string | Yes* | Server URL (required for http/sse/streamable-http protocols) |
headers | object | No | HTTP headers for HTTP-based protocols |
working_dir | string | No | Working directory for stdio servers, or for the locally-launched child of an HTTP/SSE server (default: current directory) |
env | object | No | Environment variables for stdio servers, or for the locally-launched child of an HTTP/SSE server |
launcher_wait_timeout | duration | No | When command is set together with an HTTP/SSE url, how long mcpproxy waits for that URL to become reachable after spawning the child (e.g. "15s", default "30s") |
health_check_interval | duration | No | Per-server override for the global health_check_interval. "0s" disables the liveness probe for this server only. Range: 5s–1h. Omit to inherit the global value. |
tool_discovery_interval | duration | No | Per-server override for the global tool_discovery_interval. Overrides the global/default cadence for this server only; "0s" disables the periodic tool-discovery sweep for this server (connect-time and reactive list_changed discovery still run). Range: 30s–24h. Omit to inherit the global value. |
init_timeout | duration | No | Per-server override for the global init_timeout — the MCP initialize handshake deadline. Raise it for an upstream that warms up (caches/indexes data) before responding to initialize (e.g. "120s", "3m"); without it such a server is killed mid-startup and, with docker run --rm, retries forever. Range: 1s–30m. Omit to inherit the global value (30s default). Settable via the upstream_servers tool and mcpproxy upstream patch --init-timeout. |
oauth | object | No | OAuth configuration (see OAuth Configuration) |
isolation | object | No | Per-server Docker isolation settings (see Docker Isolation) |
enabled | boolean | No | Enable/disable server (default: true) |
quarantined | boolean | No | Security quarantine status (default: false for manually added servers, true for LLM-added servers) |
reconnect_on_use | boolean | No | When true, tool calls to a disconnected server trigger an immediate reconnect attempt (15s timeout) before failing (default: false) |
created | string | No | ISO 8601 timestamp (auto-generated) |
updated | string | No | ISO 8601 timestamp (auto-updated) |
stdio - Standard input/output (local processes):
{
"name": "local-server",
"protocol": "stdio",
"command": "python",
"args": ["-m", "my_mcp_server"],
"working_dir": "/path/to/project"
}
http - HTTP transport:
{
"name": "remote-server",
"protocol": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer token"
}
}
sse - Server-Sent Events:
{
"name": "sse-server",
"protocol": "sse",
"url": "https://api.example.com/mcp/sse"
}
streamable-http - Streamable HTTP (MCP standard):
{
"name": "streamable-server",
"protocol": "streamable-http",
"url": "https://api.example.com/mcp"
}
auto - Auto-detect from command or url:
{
"name": "auto-server",
"protocol": "auto",
"command": "npx",
"args": ["-y", "my-server"]
}
By default command is only used for stdio servers. When you set command
together with an HTTP/SSE url and an explicit protocol of http, sse,
or streamable-http, mcpproxy will:
args, env, working_dir, and Docker isolation
exactly like a stdio server).launcher_wait_timeout (default 30s) for url to accept a TCP
connection.SIGTERM, then
SIGKILL after a grace period) on disconnect, restart, server-disable, or
mcpproxy shutdown. Unexpected exits trigger an automatic disconnect, which
the existing reconnect path picks up.{
"name": "local-http-mcp",
"protocol": "http",
"url": "http://127.0.0.1:9999/mcp",
"command": "node",
"args": ["./examples/echo-http-server.js", "--port", "9999"],
"working_dir": "/path/to/repo",
"launcher_wait_timeout": "15s",
"enabled": true
}
stdout and stderr of the child are routed to the per-server log, so
mcpproxy upstream logs <name> continues to work the same way it does for
stdio servers.
command and url are setprotocol | command | url | Behaviour |
|---|---|---|---|
stdio (explicit) | set | any | Stdio transport, child via stdin/stdout — url ignored. |
http / sse / streamable-http (explicit) | set | set | Locally-launched HTTP/SSE — spawn child, wait for URL, connect via network. |
http / sse / streamable-http (explicit) | unset | set | Connect to remote URL — no spawn. |
auto or unset | set | any | Stdio (command wins over url for back-compat — set protocol explicitly to opt into the launcher). |
auto or unset | unset | set | HTTP/SSE remote — no spawn. |
The "command wins" rule under auto is intentional: it preserves backwards
compatibility with configurations written before the launcher feature
existed. To launch a local HTTP/SSE server you must set protocol
explicitly to one of http, sse, or streamable-http.
{
"oauth": {
"client_id": "your-client-id",
"client_secret": "secret-reference",
"redirect_uri": "http://localhost:8080/oauth/callback",
"scopes": ["repo", "user"],
"pkce_enabled": true
}
}
| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | No | OAuth client ID (uses Dynamic Client Registration if empty) |
client_secret | string | No | OAuth client secret (can reference secure storage) |
redirect_uri | string | No | OAuth redirect URI (auto-generated if not provided) |
scopes | array | No | OAuth scopes to request |
pkce_enabled | boolean | No | PKCE is always enabled for security; this flag is currently ignored |
See OAuth Documentation for complete details.
{
"api_key": "your-secret-api-key",
"read_only_mode": false,
"disable_management": false,
"allow_server_add": true,
"allow_server_remove": true
}
| Field | Type | Default | Description |
|---|---|---|---|
api_key | string | Auto-generated | API key for REST API authentication. Required; if empty, one is auto-generated and enforced (logged on startup) |
read_only_mode | boolean | false | Prevent all configuration modifications |
disable_management | boolean | false | Disable server management operations (restart, enable, disable) |
allow_server_add | boolean | true | Allow adding new servers via API/tools |
allow_server_remove | boolean | true | Allow removing servers via API/tools |
Security Notes:
--api-key flag, MCPPROXY_API_KEY environment variable, or config filesecurity)The deterministic, offline tpa-descriptions baseline scanner always runs and is
the sole source of the approval verdict. The heavier Docker-based scanner plugins
and published-package-source extraction live behind the opt-in security.deep_scan
block — off by default, best-effort, and unable to change the baseline verdict
(Spec 077).
{
"security": {
"scan_timeout_default": "60s",
"integrity_check_interval": "1h",
"integrity_check_on_restart": false,
"scanner_registry_url": "",
"deep_scan": {
"enabled": false,
"fetch_package_source": true,
"disable_no_new_privileges": false,
"scanners": []
}
}
}
| Field | Type | Default | Description |
|---|---|---|---|
deep_scan.enabled | boolean | false | Master opt-in for the heavy layer. When false, no Docker scanner runs and no source extraction is attempted — only the in-process baseline scanner executes. |
deep_scan.fetch_package_source | boolean | true (when deep scan is on) | Whether the scanner fetches (never executes) the published source of npx/uvx package-runner servers when no local source is available. Set false for air-gapped deployments. |
deep_scan.disable_no_new_privileges | boolean | false | Omits --security-opt no-new-privileges from scanner container runs (snap-docker/AppArmor escape hatch). |
deep_scan.scanners | string[] | [] | Optional allow-list of deep scanner ids. Empty ⇒ all enabled deep scanners are eligible. |
Deprecated-key migration. The old top-level security.scanner_fetch_package_source
and security.scanner_disable_no_new_privileges keys still parse and are migrated
into security.deep_scan.* on load. The former security.auto_scan_quarantined
key was removed; a config still carrying it loads without error and the key is
ignored.
See Security Scanner Plugins for the full scanner configuration reference.
The tokenizer provides local token counting using the tiktoken library. It does not access LLMs or make API calls—it's purely for counting tokens in text locally.
{
"tokenizer": {
"enabled": true,
"default_model": "gpt-4",
"encoding": "cl100k_base"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable token counting |
default_model | string | "gpt-4" | Default model name for tokenization (used to determine encoding when model not specified) |
encoding | string | "cl100k_base" | Default tiktoken encoding to use |
Important: The tokenizer does not access LLMs. It performs local token counting using the tiktoken algorithm:
default_model field is used to look up the appropriate encoding via GetEncodingForModel()encoding field or cl100k_baseThe tokenizer automatically maps model names to encodings:
GPT-4o Series (uses o200k_base):
gpt-4o, gpt-4o-mini, gpt-4.1, gpt-4.5, gpt-4o-2024-05-13, gpt-4o-2024-08-06GPT-4 & GPT-3.5 Series (uses cl100k_base):
gpt-4, gpt-4-turbo, gpt-3.5-turbo, gpt-3.5-turbo-16k, text-embedding-ada-002, etc.Claude Models (uses cl100k_base as approximation):
claude-3-5-sonnet, claude-3-opus, claude-3-sonnet, claude-3-haiku, claude-2.1, claude-2.0, claude-instantcl100k_base as an approximation. For accurate counts, use Anthropic's count_tokens API.Codex Series (uses p50k_base):
code-davinci-002, code-davinci-001, code-cushman-002, code-cushman-001Older GPT-3 Series (uses r50k_base):
text-davinci-003, text-davinci-002, davinci, curie, babbage, ada| Encoding | Models | Description |
|---|---|---|
o200k_base | GPT-4o, GPT-4.5 | Latest OpenAI models |
cl100k_base | GPT-4, GPT-3.5, Claude (approx) | Most common encoding |
p50k_base | Codex | Code generation models |
r50k_base | GPT-3 | Legacy models |
For GPT-4:
{
"tokenizer": {
"enabled": true,
"default_model": "gpt-4",
"encoding": "cl100k_base"
}
}
For Claude Models:
{
"tokenizer": {
"enabled": true,
"default_model": "claude-3-5-sonnet",
"encoding": "cl100k_base"
}
}
For GPT-4o:
{
"tokenizer": {
"enabled": true,
"default_model": "gpt-4o",
"encoding": "o200k_base"
}
}
Disable Token Counting:
{
"tokenizer": {
"enabled": false
}
}
{
"tls": {
"enabled": false,
"require_client_cert": false,
"certs_dir": "",
"hsts": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable HTTPS/TLS |
require_client_cert | boolean | false | Enable mutual TLS (mTLS) for client authentication |
certs_dir | string | "" | Custom certificate directory (defaults to ${data_dir}/certs) |
hsts | boolean | true | Enable HTTP Strict Transport Security headers |
Quick Setup:
mcpproxy trust-cert"enabled": true or MCPPROXY_TLS_ENABLED=truehttps://See Setup Guide - HTTPS for complete details.
{
"logging": {
"level": "info",
"enable_file": false,
"enable_console": true,
"filename": "main.log",
"log_dir": "",
"max_size": 10,
"max_backups": 5,
"max_age": 30,
"compress": true,
"json_format": false
}
}
| Field | Type | Default | Description |
|---|---|---|---|
level | string | "info" | Log level: trace, debug, info, warn, error |
enable_file | boolean | false | Enable file logging |
enable_console | boolean | true | Enable console logging |
filename | string | "main.log" | Log filename |
log_dir | string | "" | Custom log directory (defaults to OS log root; see below) |
max_size | integer | 10 | Maximum log file size in MB before rotation |
max_backups | integer | 5 | Number of backup log files to keep |
max_age | integer | 30 | Maximum age of log files in days |
compress | boolean | true | Compress rotated log files |
json_format | boolean | false | Use JSON format (useful for log aggregation) |
Log Locations (defaults):
~/Library/Logs/mcpproxy/main.log~/.local/state/mcpproxy/logs/main.log (or /var/log/mcpproxy when running as root)%LOCALAPPDATA%\mcpproxy\logs\main.logserver-{name}.log (characters in the server name that aren't letters, digits, ., -, or _ — such as the / in registry names like io.github.evidai/polymarket-guard — are sanitized to _, so the log is always a single flat file)log_dir to override (supports ~ expansion)Behavior notes:
mcpproxy serve enables file logging by default unless --log-to-file is explicitly set to falseSee Logging Documentation for complete details.
{
"docker_isolation": {
"enabled": false,
"default_images": {
"python": "python:3.11",
"node": "node:20",
"npx": "node:20"
},
"registry": "docker.io",
"network_mode": "bridge",
"memory_limit": "512m",
"cpu_limit": "1.0",
"timeout": "30s",
"extra_args": [],
"log_driver": "",
"log_max_size": "100m",
"log_max_files": "3"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable Docker isolation globally |
default_images | object | See below | Map of runtime type to Docker image |
registry | string | "docker.io" | Docker registry to use |
network_mode | string | "bridge" | Docker network mode |
memory_limit | string | "512m" | Memory limit for containers |
cpu_limit | string | "1.0" | CPU limit (1 core) |
timeout | string | "30s" | Container startup timeout |
extra_args | array | [] | Additional docker run arguments |
log_driver | string | "" | Docker log driver (empty = system default) |
log_max_size | string | "100m" | Maximum log file size |
log_max_files | string | "3" | Maximum number of log files |
{
"python": "python:3.11",
"python3": "python:3.11",
"uvx": "python:3.11",
"pip": "python:3.11",
"pipx": "python:3.11",
"node": "node:20",
"npm": "node:20",
"npx": "node:20",
"yarn": "node:20",
"go": "golang:1.21-alpine",
"cargo": "rust:1.75-slim",
"rustc": "rust:1.75-slim",
"binary": "alpine:3.18",
"sh": "alpine:3.18",
"bash": "alpine:3.18",
"ruby": "ruby:3.2-alpine",
"gem": "ruby:3.2-alpine",
"php": "php:8.2-cli-alpine",
"composer": "php:8.2-cli-alpine"
}
{
"mcpServers": [
{
"name": "isolated-server",
"isolation": {
"enabled": true,
"image": "custom-image:latest",
"network_mode": "none",
"extra_args": ["--cap-drop=ALL"],
"working_dir": "/app",
"log_driver": "json-file",
"log_max_size": "50m",
"log_max_files": "2"
}
}
]
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Enable Docker isolation for this server (overrides global setting) |
image | string | Custom Docker image (overrides default) |
network_mode | string | Custom network mode for this server |
extra_args | array | Additional docker run arguments |
working_dir | string | Working directory inside container |
log_driver | string | Log driver override |
log_max_size | string | Log file size override |
log_max_files | string | Log file count override |
See Docker Isolation Documentation for complete details.
{
"docker_recovery": {
"enabled": true,
"check_intervals": ["2s", "5s", "10s", "30s", "60s"],
"max_retries": 0,
"notify_on_start": true,
"notify_on_success": true,
"notify_on_failure": true,
"notify_on_retry": false,
"persistent_state": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable Docker recovery monitoring |
check_intervals | array | ["2s", "5s", "10s", "30s", "60s"] | Exponential backoff intervals for health checks |
max_retries | integer | 0 | Maximum retry attempts (0 = unlimited) |
notify_on_start | boolean | true | Show notification when recovery starts |
notify_on_success | boolean | true | Show notification on successful recovery |
notify_on_failure | boolean | true | Show notification on recovery failure |
notify_on_retry | boolean | false | Show notification on each retry |
persistent_state | boolean | true | Save recovery state across restarts |
See Docker Recovery Documentation for complete details.
{
"environment": {
"inherit_system_safe": true,
"allowed_system_vars": [
"PATH",
"HOME",
"TMPDIR",
"NODE_PATH"
],
"custom_vars": {
"CUSTOM_VAR": "value"
},
"enhance_path": false
}
}
| Field | Type | Default | Description |
|---|---|---|---|
inherit_system_safe | boolean | true | Inherit safe system environment variables |
allowed_system_vars | array | See below | List of system variables to allow |
custom_vars | object | {} | Custom environment variables to set |
enhance_path | boolean | false | Enable PATH enhancement for Launchd scenarios |
Default Allowed System Variables:
PATH, HOME, TMPDIR, TEMP, TMP, SHELL, TERM, LANG, USER, USERNAMEUSERPROFILE, APPDATA, LOCALAPPDATA, PROGRAMFILES, SYSTEMROOT, COMSPECXDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME, XDG_RUNTIME_DIRLC_* variables (e.g., LC_ALL, LC_CTYPE, …)custom_vars merged on topProxy variables are never inherited by default.
HTTP_PROXY,HTTPS_PROXY,NO_PROXY,ALL_PROXY, andFTP_PROXYare deliberately excluded from the default allow-list because proxy URLs commonly embed credentials (http://user:pass@proxy). To forward them to upstream stdio servers, opt in withforward_proxy_env(see below).
{
"forward_proxy_env": true
}
| Field | Type | Default | Description |
|---|---|---|---|
forward_proxy_env | boolean | false | Forward ambient proxy environment variables to spawned stdio upstream servers, with credentials redacted |
When forward_proxy_env is true, mcpproxy forwards the proxy variables present
in its own environment (HTTP_PROXY/http_proxy, HTTPS_PROXY/https_proxy,
NO_PROXY/no_proxy, ALL_PROXY/all_proxy, FTP_PROXY/ftp_proxy — both
spellings are recognized) to each spawned stdio upstream. Any userinfo
(user:password@) is stripped from the value before forwarding, so
credentials never reach upstream servers while the proxy host/port is preserved.
An explicitly configured proxy value (via custom_vars or a server's env)
always takes precedence and suppresses forwarding of the ambient value, including
the other-cased alias.
macOS GUI/launchd note: when launched from the Dock/Launchpad or the login item, mcpproxy inherits a minimal environment that may not contain your proxy variables. In that case set the proxy explicitly under
custom_varsor a server'senvblock.
Controls how upstream MCP tools are exposed to AI agents on the default /mcp endpoint.
{
"routing_mode": "retrieve_tools"
}
| Field | Type | Default | Description |
|---|---|---|---|
routing_mode | string | "retrieve_tools" | How tools are exposed: retrieve_tools, direct, or code_execution |
Available modes:
| Mode | Description |
|---|---|
retrieve_tools | BM25 search via retrieve_tools + call_tool_read/write/destructive (default, most token-efficient) |
direct | All upstream tools exposed directly as serverName__toolName |
code_execution | JavaScript orchestration via code_execution tool with tool catalog |
All three modes are always available on dedicated endpoints regardless of config: /mcp/all (direct), /mcp/code (code_execution), /mcp/call (retrieve_tools).
See Routing Modes for complete details.
Text returned in the MCP initialize response to guide AI agents on how to use the proxy (e.g., use retrieve_tools to discover existing tools rather than search_servers).
{
"instructions": "Use retrieve_tools to discover tools before assuming a capability is unavailable."
}
| Field | Type | Default | Description |
|---|---|---|---|
instructions | string | (built-in) | Custom instructions sent in the MCP initialize response. When empty, a built-in default explains the retrieve_tools → call_tool_* workflow and warns against using search_servers for existing tools. |
You can edit this from the Web UI under Settings → Advanced → MCP server instructions. The textarea shows the built-in default as a greyed-out placeholder; clearing it restores that default.
Note: Applied at startup / on the next client connect — editing this value does not hot-reload into already-connected MCP sessions.
SHA256 hash-based tool approval system that detects changes to tool descriptions and schemas.
{
"quarantine_enabled": true
}
| Field | Type | Default | Description |
|---|---|---|---|
quarantine_enabled | boolean | true | Enable tool-level quarantine globally |
Per-server tool-change auto-approval is configured on the server entry:
{
"mcpServers": [
{
"name": "trusted-server",
"command": "my-server",
"skip_quarantine": true
}
]
}
| Field | Type | Default | Description |
|---|---|---|---|
auto_approve_tool_changes | boolean (tri-state) | unset (= false) | Auto-approve all post-baseline tool changes AND additions for this server (disables per-server rug-pull protection). The active per-server control. A trusted server's baseline is auto-approved regardless of this flag. |
skip_quarantine | boolean | false | Deprecated — superseded by auto_approve_tool_changes. A legacy skip_quarantine: true is migrated onto auto_approve_tool_changes on load only when it is unset (an explicit false overrides the legacy flag). |
See Tool Quarantine for complete details.
{
"enable_code_execution": false,
"code_execution_timeout_ms": 120000,
"code_execution_max_tool_calls": 0,
"code_execution_pool_size": 10
}
| Field | Type | Default | Description |
|---|---|---|---|
enable_code_execution | boolean | false | Enable JavaScript/TypeScript code execution tool (disabled by default for security) |
code_execution_timeout_ms | integer | 120000 | Default timeout in milliseconds (1-600000, max 10 minutes) |
code_execution_max_tool_calls | integer | 0 | Maximum tool calls per execution (0 = unlimited) |
code_execution_pool_size | integer | 10 | Number of JavaScript VM instances in pool (1-100) |
Code execution supports both JavaScript (ES2020+) and TypeScript. TypeScript code is automatically transpiled via esbuild before execution.
See Code Execution Documentation for complete details.
{
"features": {
"enable_runtime": true,
"enable_event_bus": true,
"enable_sse": true,
"enable_observability": true,
"enable_health_checks": true,
"enable_metrics": true,
"enable_tracing": false,
"enable_oauth": true,
"enable_quarantine": true,
"enable_docker_isolation": false,
"enable_search": true,
"enable_caching": true,
"enable_async_storage": true,
"enable_web_ui": true,
"enable_debug_logging": false,
"enable_contract_tests": false
}
}
Note: Feature flags are typically managed internally. Most users don't need to modify these settings.
The three default registries ship built-in and require no configuration. Use
the registries array only to add your own custom source:
{
"registries": [
{
"id": "mycorp",
"name": "My Corp Registry",
"description": "Internal MCP server catalog",
"url": "https://registry.mycorp.example/",
"servers_url": "https://registry.mycorp.example/v0.1/servers",
"tags": ["internal"],
"protocol": "modelcontextprotocol/registry"
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique registry identifier |
name | string | Display name |
description | string | Registry description |
url | string | Registry homepage |
servers_url | string | API endpoint for server listings |
tags | array | Registry tags (e.g., ["verified"]) |
protocol | string | Registry protocol type |
count | number/string | Number of servers in registry (auto-populated) |
SSRF guard (allow_private_registry_fetch). Because the daemon fetches the
URL you configure, registry fetches refuse any host that is — or resolves to — a
non-routable address (loopback, RFC1918/CGNAT private, link-local including the
169.254.169.254 cloud-metadata endpoint). This bounds CWE-918 request forgery
against internal services. Set this top-level flag to true only if you
intentionally run a trusted registry mirror on an internal/private address:
{ "allow_private_registry_fetch": true }
⚠️ The opt-out is blanket (all-or-nothing). Setting it
truelifts the guard for every non-routable range at once — loopback, RFC1918/CGNAT private, link-local and the169.254.169.254cloud-metadata endpoint. There is no way to allow only loopback: enabling it for a localhost dev registry also re-opens the cloud-metadata SSRF vector (e.g.registry add-source https://169.254.169.254/...will then succeed). Enable it only for trusted local/dev use, ideally on hosts with no cloud-metadata exposure. The flag takes effect only on daemon (re)start / config reload.
Default false (secure). See Registries Documentation.
Default Registries (shipped built-in, no configuration required):
official — Official MCP Registry (modelcontextprotocol/registry): primary, zero-config aggregatorreference — Reference Servers (builtin/reference): curated @modelcontextprotocol servers, shipped in-binary so the basics work offlinedocker-mcp-catalog — Docker MCP Catalog (custom/docker): signed-container MCP server inventoryDeprecated former-defaults: earlier versions also shipped
pulse,smithery,fleur,azure-mcp-demo, andremote-mcp-serversas defaults. These were removed and are pruned from an existingmcp_config.jsonon load, so upgrades converge to the three defaults above. Genuinely user-added custom registries are never touched;pulse/smitherycan be added back as custom sources.
See Registries Documentation and Search Servers Documentation for complete details.
Controls the usage-statistics aggregate that powers the Web UI usage graphs (spec 069). The aggregate is built incrementally from the activity log, kept in memory as an immutable snapshot, and periodically persisted so it survives restarts without a full re-scan.
{
"observability": {
"usage_cache_ttl": "5s",
"usage_persist_interval": "30s"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
usage_cache_ttl | duration string | 5s | Freshness bound for the usage endpoint's read cache on wide time windows. |
usage_persist_interval | duration string | 30s | How often the in-memory usage aggregate snapshot is flushed to storage (also flushed on graceful shutdown). |
Both fields are optional, accept Go duration strings (e.g. "10s", "1m"),
and are hot-reloadable. Non-positive values fall back to the defaults.
Controls the background upgrade-awareness checker (Spec 079). MCPProxy
periodically queries GitHub Releases and surfaces "update available" on
mcpproxy status / doctor, a startup log line, the Web UI (sidebar badge +
dismissible banner), and the trays. Checks never block and fail silently when
offline.
{
"update_check": {
"enabled": true,
"channel": "stable"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch for update checking. When false, no network check is performed (background poll and the manual /api/v1/info?refresh=true re-check) and no upgrade nudge appears on any surface — the update object is omitted from /api/v1/info. |
channel | string | "stable" | Release channel: "stable" (GitHub releases/latest; prereleases never offered) or "rc" (prerelease tags such as v0.47.0-rc.1 included). |
Both keys are optional and hot-reloadable: editing them (config file or
POST /api/v1/config/apply) takes effect without a restart, and re-enabling
triggers a prompt re-check.
Environment-variable precedence — the existing switches keep working and win over the config keys (operator override):
| Variable | Effect |
|---|---|
MCPPROXY_DISABLE_AUTO_UPDATE=true | Force-disables update checking even when update_check.enabled is true. |
MCPPROXY_ALLOW_PRERELEASE_UPDATES=true | Force-selects the prerelease (rc) channel even when update_check.channel is stable. |
The env vars only widen in one direction (disable checks / enable
prereleases); they cannot force-enable checking that config disabled — with
update_check.enabled: false, checks stay off regardless of environment.
See Version Updates for where updates are surfaced.
Here's a complete configuration example with all major sections:
Note: Leaving api_key empty will cause MCPProxy to generate and enforce a new key on startup.
{
"listen": "127.0.0.1:8080",
"data_dir": "~/.mcpproxy",
"enable_socket": true,
"api_key": "",
"tools_limit": 15,
"tool_response_limit": 20000,
"call_tool_timeout": "2m",
"debug_search": false,
"enable_prompts": true,
"check_server_repo": true,
"tokenizer": {
"enabled": true,
"default_model": "gpt-4",
"encoding": "cl100k_base"
},
"tls": {
"enabled": false,
"require_client_cert": false,
"hsts": true
},
"logging": {
"level": "info",
"enable_file": false,
"enable_console": true,
"filename": "main.log",
"max_size": 10,
"max_backups": 5,
"max_age": 30,
"compress": true,
"json_format": false
},
"mcpServers": [
{
"name": "everything",
"protocol": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"enabled": true,
"quarantined": false
},
{
"name": "github",
"protocol": "http",
"url": "https://api.github.com/mcp",
"oauth": {
"scopes": ["repo", "user"],
"pkce_enabled": true
},
"enabled": true
}
],
"docker_isolation": {
"enabled": false
},
"docker_recovery": {
"enabled": true,
"notify_on_start": true,
"notify_on_success": true,
"notify_on_failure": true
},
"environment": {
"inherit_system_safe": true,
"allowed_system_vars": ["PATH", "HOME", "TMPDIR"],
"custom_vars": {},
"enhance_path": false
},
"enable_code_execution": false,
"code_execution_timeout_ms": 120000,
"code_execution_max_tool_calls": 0,
"code_execution_pool_size": 10,
"read_only_mode": false,
"disable_management": false,
"allow_server_add": true,
"allow_server_remove": true
}
Many configuration options can be overridden via environment variables:
| Environment Variable | Config Field | Description |
|---|---|---|
MCPPROXY_LISTEN / MCPP_LISTEN | listen | Network binding address |
MCPPROXY_API_KEY | api_key | API key for authentication (empty values trigger auto-generation; auth remains enabled) |
MCPPROXY_TLS_ENABLED | tls.enabled | Enable HTTPS/TLS |
MCPPROXY_TLS_REQUIRE_CLIENT_CERT | tls.require_client_cert | Enable mTLS |
MCPPROXY_CERTS_DIR | tls.certs_dir | Custom certificates directory |
MCPPROXY_DATA | data_dir | Override data directory |
MCPPROXY_DISABLE_OAUTH | - | Disable OAuth for testing |
HEADLESS | - | Run in headless mode |
Prefix rules:
MCPP_ prefix (hyphens become underscores), e.g., MCPP_TOOLS_LIMIT, MCPP_ENABLE_PROMPTS.MCPPROXY_ prefix as listed above.Priority: Environment variables > Config file > Defaults
MCPProxy validates configuration on startup. Common validation errors:
host:port or :port formatstdio, http, sse, streamable-http, or autocommand fieldurl field"30s", "2m")Run mcpproxy doctor to check configuration health.