Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

Every number in mcpx is a setting, including the ones nobody will ever change.

That sounds like an overreaction until you have hit the alternative. A constant written next to the code that needs it is fine right up to the day somebody’s proxy kills an idle event stream at thirty seconds, or their sandbox refuses to exec the running binary, or a tool legitimately takes a five-megabyte argument. At that point the only remedies are a patched binary or a fork, and both are absurd outcomes for a number that was chosen in five seconds and never revisited. Declaring it costs one line and removes that outcome entirely.

So the rule is: nothing is hardcoded. A value lives in internal/defaults/defaults.json as data, and – wherever a user could conceivably want it different – it is declared once in the settings registry, from which its configuration key, its environment variable, its command-line flag, its /v1 representation and its MCP tool are all derived. There is no second place to update, which is the point: the arrangement this replaced had four, and they drifted.

The handful of exceptions are permission bits. 0700 on the state directory and 0600 on the socket are not preferences. The socket grants the power to run tools as you; a configuration key that widens it would be a footgun with no legitimate use. They are in defaults.json as data so there is still one place to read them, and they are deliberately absent from the registry.

A guard test enforces this. It matches inline durations in both spellings gofmt produces, byte sizes written as shifts, bounds assigned to variables whose names say they are bounds, and permission literals, with an allowlist that carries a reason for each exemption. The version it replaced matched fixed strings and missed every duration gofmt had compacted into a function argument – nine of them, including the daemon’s shutdown grace and the event stream’s keepalive.

Where a value can come from

Lowest to highest:

  1. default – internal/defaults/defaults.json, embedded in the binary.
  2. file – every configuration file on the search path, ranked by distance, nearest highest. mcpx config --sources lists them.
  3. env – MCPX_*. Every setting has one, derived from its path, whether or not anybody uses it.
  4. flag – the command line.
  5. runtime – an override set through PUT /v1/settings/{path} or mcpx settings set. Highest because it is the most recent statement of intent: somebody changed their mind while the thing was running, and having a flag from ten minutes ago win would be inexplicable.

mcpx settings get <path> prints the winning value, where it came from, and what it displaced. “Why is this not what my config says” is the most common configuration question there is, and the answer is always in that list.

The search path itself is decided before any of those layers exist, in two passes. The discovery pass walks the default locations, seeded by MCPX_PATHS_CONFIG / --paths-config, and reads only paths.config. The load pass resolves that list and loads everything on it, nearest last: a file’s paths.config governs the rest of the chain from that point, a null entry stands for the locations the default walk provided, and a list with no null replaces the farther remainder. A paths.config found only in the load pass is reported by mcpx config --sources, not applied, and a cycle is refused naming the path that closed it. MCPX_CONFIG bypasses both passes and loads exactly one file.

Two spellings of one setting at the same level is an error rather than a race: --log-level and --logging-level together, or two variables that mean the same thing with different values. The environment has no order, so there is genuinely nothing to prefer, and picking one silently is how a configuration becomes unexplainable.

Scope: who reads it

Every setting says which process acts on it. This exists because of a failure with no symptom.

A daemon is started once, in whatever environment it happened to have, and then serves every command for hours. A flag on one of those commands that governs something the daemon does was simply lost: accepted, validated, printed back, and then ignored, because the process that would act on it had read its configuration an hour earlier. Nothing anywhere said so.

  • daemon – the pooled servers, the listener, the log the daemon writes. A client changes one by persisting it and restarting, or through mcpx settings set if the setting is hot.
  • client – the CLI process itself: how it renders, how it finds a daemon, whether it may start one. Setting it on the daemon does nothing.
  • call – meaningful per request. The CLI sends its effective value with every request in an X-Mcpx-Settings header, and the daemon honours it for that request only. This is what makes mcpx search --limit 3 work against a daemon that was started without it. Only call-scoped settings are honoured from that header: widening it would make a header a way for any client to reconfigure a shared daemon.
  • plugin – read by the opencode plugin, which is neither. The plugin reads process.env directly, so the variable names are the contract between the two and a test pins them.

Hot: does it take effect now

A hot setting is read afresh every time it is used, so changing it at runtime works. A cold one was consumed once at startup – a listener address, a log file that is already open – and recording a change to it would do nothing. PUT /v1/settings/{path} says so rather than accepting a value that would be silently inert.

Reaching them

surfacereadchange
config fileany mcpx config on the search pathedit it
environmentMCPX_*export it
CLImcpx settings list, mcpx settings get <path>mcpx settings set <path> <value> [--persist runtime|project|user], mcpx settings unset <path>
/v1GET /v1/settings, GET /v1/settings/{path}PUT /v1/settings/{path}, DELETE /v1/settings/{path}
MCPmcpx_settings_list, mcpx_settings_getmcpx_settings_set, mcpx_settings_unset

mcpx settings list resolves locally, which is what the client will use. --daemon asks the daemon what it resolved, which is what a daemon-scoped setting actually is. The two differing is information, not a bug: it is exactly the case that used to be invisible.

--persist runtime changes the running daemon and nothing on disk, which is what an experiment wants. project and user write the corresponding configuration file and apply at once where the setting allows it – a change written to a file but not applied would leave mcpx settings get answering with the old value, which reads as the write having failed.

Local-model controls by surface

These are independent controls, not one fleet-wide Qwen switch. In particular, parking hosted chat-model listings does not disable embeddings or an interpreter. The checked-in state below is from this repository’s main configuration, not a guarantee about a running process: nearer files, environment, flags and runtime overrides can change it. See #2218 for the remaining single-switch work.

SurfaceControl and checked-in stateWhat it does not control
mcpx catalog embeddingsembeddings.backend defaults to auto; .mcpx.json sets no embeddings block. With no URL or WASM path configured there, the cascade reaches the built-in pure-Go subword engine, not a Qwen model. local forces that engine.Prompt generation, OpenCode messaging and provider listings. auto can use a configured remote or WASM model; it is not a global local-model off switch.
mcpx prompt generationprompt.sample defaults to never; model.provider defaults to none. Neither is set in .mcpx.json. The file does contain a legacy repair block naming opencode and claude-code/claude-haiku-5-5, not Qwen.Embeddings or arbitrary models chosen by a caller or external OpenCode.
OpenCode messaging embeddings.opencode/opencode.jsonc sets messaging.embedding to "off". Removing that setting restores the OpenCode default, which starts its Qwen3-Embedding local server.Chat provider listings or mcpx embeddings.
OpenCode chat provider listingsQwen definitions for cline-pass and commandcode are parked as comments; other Qwen/Llama listings are under disabled providers or commented blocks.Messaging embeddings. Restoring the parked blocks requires editing them: there is not yet a single switch restoring all listings.
OpenChamber route interpreterThe separate local route-interpreter stage is controlled in OpenChamber Settings; its Qwen2.5 model requires a download and was reported absent on the laptop in the #2218 audit.OpenCode provider listings and mcpx. This is a separate cross-repository remainder, not inherited state.

For catalog embeddings, auto tries the configured embeddings.url remote endpoint, then a successfully loaded embeddings.wasmPath, then the subword engine. remote and wasm each fall back to subword on failure; local uses subword directly. A remote endpoint may itself be local (for example Ollama): the backend name does not say where the model runs. embeddings.model names the remote model, not a local download. These settings select an embedder; they do not switch semantic search itself off.

For prompt generation, prompt.sample: never prevents an unmatched-recipe request from asking a model by default; ask allows it. An explicit request provider or model, or a non-none model.provider, also opts in even when prompt.sample is never. model.provider selects the newer model layer (none, auto, sampling, plugin, openai, anthropic, or google), with model.baseURL and model.default for a configured endpoint and model. An OpenAI-compatible local endpoint can serve Qwen, but it must be configured separately. model.provider: none alone is not a universal veto: the legacy scripting provider can resolve an explicit request, environment or installed CLI when generation is allowed. The legacy repair.provider and repair.model keys in the fleet file are not registered settings in this version, so do not assume that block selects the effective provider. Per-request mcpx prompt --provider / --model can also select a provider. None of these controls constrain independent callers of external services.

Use mcpx settings get embeddings.backend --daemon, mcpx settings get prompt.sample --daemon, and mcpx settings get model.provider --daemon to inspect the daemon’s resolved settings; check mcpx config --sources and the daemon environment as well. Embedding backends are constructed when settings are installed, so verify the running daemon after changing configuration rather than assuming an existing index or embedder has switched. Model provider and prompt sampling settings are daemon-scoped and cold.

Servers

Servers are configuration too, and until now adding one meant an editor and a restart.

surfacelistaddremove
CLImcpx servers listmcpx servers add <name> -- <cmd>..., mcpx registry add <n> --writemcpx servers remove <name>
/v1GET /v1/serversPOST /v1/serversDELETE /v1/servers/{name}
MCPmcpx_servers_listmcpx_servers_addmcpx_servers_remove

The entry is written to a configuration file and the daemon reloads, so the server is callable immediately and is still there tomorrow. A server whose process definition did not change keeps its running child: adding one server must not restart the rest, or a stateful server – a browser holding a session – loses it because a neighbour appeared.

The daemon also re-reads its configuration files when they change on disk, checked on the same tick that reaps idle instances (daemon.watchConfig). An edit by hand therefore takes effect within daemon.reapInterval without anything being restarted.

Making that work required changing what identifies a daemon. The socket used to be keyed to the contents of the config files, so that editing one got a fresh daemon rather than a stale one. A daemon that reloads changes the contents of the files it is keyed by, which moves its own key, which makes it unreachable by the client that just edited it – and hands the next command a different daemon that happens to match the new key with old servers loaded. That was observed: mcpx servers remove succeeded, the file was correct, and the next mcpx servers list showed the removed server. The key is now the file set, and staleness is handled where it belongs.

Hiding tools

tools (an allowlist) and excludeTools (a denylist) go in a server’s mcpx block, or in the top-level pool block to apply to every server. A hidden tool is left out of every listing – mcpx tools, generated types, search, the MCP pass-through – and a call to it by name is refused with 400.

{
  "pool": { "excludeTools": ["delete_*"] },
  "mcpServers": {
    "datadog": { "command": "mcp-remote", "args": ["..."], "mcpx": { "excludeTools": ["execute_*", "/^(update|upsert)_.*dashboard/"] } }
  }
}

Each entry is one of:

entrymeansexample
/.../a Go regular expression, unanchored as regexps are/delete/, /^delete_/
contains *, ? or [a glob over the whole name: * any run, ? one character, [a-z] / [!a-z] a classdelete_*, *_monitor
anything elsethe exact tool namedelete_datadog_workflow

A pattern that cannot compile is a configuration error naming the field, not a rule that silently matches nothing.

Constraining calls by their arguments

Hiding decides by tool name: a tool is callable with any arguments or not at all. argumentRules decides by the call: a per-server map in the mcpx block – also allowed in the top-level pool block – whose keys are tool patterns matched exactly as tools/excludeTools match (exact name, glob, or /regexp/), and whose values are JSON Schema objects. A call is allowed only if its arguments validate against every rule whose key matches the tool. Pool rules and server rules add up, and so do rules from layered config files: rules only tighten, and a nearer file cannot loosen a farther one.

{
  "mcpServers": {
    "github": {
      "command": "...",
      "mcpx": {
        "argumentRules": {
          "create_repository": {
            "required": ["private", "organization"],
            "properties": {
              "private": { "const": true },
              "organization": { "pattern": "^(my-org|my-org-sandbox)$" }
            }
          },
          "/^(push_files|create_or_update_file|delete_file)$/": {
            "required": ["branch"],
            "properties": { "branch": { "not": { "enum": ["main", "master"] } } }
          },
          "*": {
            "properties": { "owner": { "pattern": "^(my-org|my-user)$" } }
          }
        }
      }
    }
  }
}

Per-argument rules are the same mechanism: a "*" key whose schema is a properties object constrains that argument on every tool that carries it, and leaves tools without the argument untouched.

A refused call is a 400, as a hidden tool is, before the server is started: the message names the argument path, the value sent, the constraint it broke, and where the rule came from (config file, server, key). Over the MCP pass-through the same refusal arrives as a tool result with isError: true. Listings advertise the tightened schema: the inputSchema shown by mcpx types, the generated client, search, and the pass-through’s tools/list is the upstream schema allOf every matching rule, so an agent sees private: true before it ever calls.

A rule whose key matches no tool on its server warns at load and on catalog changes – a server that renames a tool must not silently disable the rule that guarded it. A value that is not a valid schema, or a pattern the validator cannot compile (JSON Schema patterns are ECMA-262; lookahead and backreferences are unsupported), is a configuration error naming the server and key, like a bad tool pattern.

Two cautions, plainly:

  • Server defaults are invisible. A rule constrains what is sent, not what the server fills in. If a constraint matters, list the argument in required – never rely on a server-side default to satisfy it.
  • Free-form arguments cannot truly be constrained. A pattern over a query/jql/sql string matches text, not meaning. A tool whose danger lives in a free-form argument belongs in excludeTools, not in a rule.

extraIncludeTools takes the same entries and adds them to the allowlist instead of replacing it, so a server can widen a pool-wide tools by one tool without restating the rest:

{
  "pool": { "tools": ["get_*", "list_*", "search_*"] },
  "mcpServers": {
    "datadog": { "mcpx": { "extraIncludeTools": ["create_datadog_monitor"] } }
  }
}

With no tools anywhere, extraIncludeTools is the allowlist – it includes, as tools does, so everything it does not name is hidden.

excludeTools wins over both. A server’s own tools replaces the pool’s; extraIncludeTools and excludeTools from the pool and the server all apply, so a server that hides one more tool does not undo a pool-wide rule. A nearer config file’s pool.extraIncludeTools likewise adds to a farther one’s. An alias (aliasOf) filters independently of the server it shares a process with.

Names are the server’s own tool names, as mcpx tools prints them in its tool column, not the generated function names.

Some servers can filter on their side too – Datadog’s omit_tools URL parameter, GitHub’s GITHUB_READ_ONLY – which keeps the tool from ever reaching mcpx. Those are each server’s own convention; this one works for any server.

Write policy (policy.write)

A tool call is a write when its tool is not annotated readOnlyHint true, read with the specification’s defaults: an unannotated tool may write, and may be destructive. The daemon-wide setting policy.write (default ask) gates every such call, between resolving an instance and using it:

modewhat happens to a write call
aska confirmation is raised showing the exact call – the tool and its arguments in canonical (key-sorted) JSON – and the call proceeds only if somebody answers true
allowthe call proceeds with no confirmation from the policy
denythe call is refused, with an error naming the tool, the mode, and the settings that can change it

A server’s own writePolicy in its mcpx block overrides the daemon mode for that server alone:

{ "mcpServers": { "datadog": { "command": "...", "mcpx": { "writePolicy": "deny" } } } }

Under ask, a caller that cannot answer questions is refused, not waited out and not let through: the error says the policy is ask, that there is no question broker to answer it, and that setting policy.write to allow (or deny) is the escape. An unanswered or refused confirmation refuses the call too.

elicit.confirmDestructive is a floor on top of this, not an alternative to it: with it on, a destructive call asks even under allow – raised by the destructive confirmation itself, with its annotation-trust rules unchanged – and the floor never weakens a deny. Under ask the policy has already asked once, so no call is asked about twice.

This gate is an operator consent gate, not the autonomy dial (docs/decisions/0002-autonomy-dial.md): the dial bounds what mcpx does on its own, the write policy bounds what a caller may do to the outside world through it, and neither clamps the other.

Every setting

Everything between the markers below is generated from settings.Registry() by TestTheDocumentationListsEverySetting, which fails when it is stale:

go test ./internal/settings -run TestTheDocumentationListsEverySetting -update

It used to say it was generated while being written by hand, and it had drifted: two settings were missing an alias their flag really answers to. mcpx config --schema --plumbing prints the same inventory, and mcpx settings list prints it with the values that are actually in force.

Entries marked (plumbing) are internals. They work and they are supported, but there is no ordinary reason to change one, so they are kept out of ordinary help rather than hidden – a flag a user can find in the source and that --help denies exists is worse than a long list.

paths.configFile is the one setting a configuration file may not set: it decides which file is read, so it is accepted from the environment and from the global --config, which goes before the command.

plumbing.strictUnknownKeys governs keys no setting claims, and keys a server entry declares that mcpx does not know — type, alwaysAllow and the rest, which belong to the other hosts that read the same file. Those are recorded, reported by mcpx doctor, and refused only when this is on.

A key inside a server’s "mcpx" block is not governed by it and is always refused. That block is mcpx’s alone — nothing else writes into it — so a key it does not know is a typo or a name that no longer exists, not another host’s field. This is deliberately not a setting: it is how mcpx init wrote "mode": "session" into every new config for months without anyone noticing, and a switch to turn the report off would have kept it quiet.

A script whose first line names its interpreter (#!) runs under that interpreter, ahead of every setting below: a -S/--split-string payload is re-split with shell quoting rules (so -S sh -c "printf hello" keeps printf hello one argument; an unterminated quote fails the run), VAR=value assignments reach the child even when the value contains /, env -u/env -i apply (the child starts from the parent’s environment minus what they remove, or from an empty one), and env -C/env --chdir moves the run, beating script.cwd the way the interpreter choice beats script.runtime. An env option mcpx does not implement fails the run rather than being dropped. A dispatched run never inherits MCPX_CLIENT, MCPX_RUNTIME, MCPX_ENTRY or MCPX_DISPATCH from an outer run, and its notice names anything else it goes without (no generated client, no enforced permission profiles, skipped launcher phases).

artifacts

settingkinddefaultscopehotflagvariablegoverns
artifacts.chunkBytesbytes256KiBdaemonno--artifacts-chunk-bytesMCPX_ARTIFACTS_CHUNK_BYTES(plumbing) how much of a body one streamed frame carries
artifacts.deliveryenumreferencecallyes--artifacts-deliveryMCPX_ARTIFACTS_DELIVERYhow artifact bodies reach the caller
artifacts.dirstring(empty)callyes--artifacts-dirMCPX_ARTIFACTS_DIRwrite this run’s artifacts here
artifacts.enabledbooltruedaemonno--artifacts-enabledMCPX_ARTIFACTS_ENABLEDwhether the daemon keeps files scripts produce
artifacts.gcIntervalduration10mdaemonno--artifacts-gc-intervalMCPX_ARTIFACTS_GC_INTERVAL(plumbing) how often expired artifacts are swept
artifacts.idleTtlduration0sdaemonno--artifacts-idle-ttlMCPX_ARTIFACTS_IDLE_TTLwhen set, reading an artifact renews it for this long; off keeps absolute expiry
artifacts.inlineMaxBytesbytes1MiBcallyes--artifacts-inline-max-bytesMCPX_ARTIFACTS_INLINE_MAX_BYTESthe largest artifact that may be base64’d into a result
artifacts.interceptImagesbooltruecallyes--artifacts-intercept-imagesMCPX_ARTIFACTS_INTERCEPT_IMAGESturn image and audio content in a result into artifact references
artifacts.maxAgeduration168hdaemonno--artifacts-max-ageMCPX_ARTIFACTS_MAX_AGEthe absolute ceiling on a renewed artifact’s life, however often it is read
artifacts.maxBytesbytes64MiBdaemonno--artifacts-max-bytesMCPX_ARTIFACTS_MAX_BYTESthe largest single artifact that may be stored
artifacts.quotabytes1GiBdaemonno--artifacts-quotaMCPX_ARTIFACTS_QUOTAthe total the artifact store may hold
artifacts.ttlduration24hdaemonno--artifacts-ttlMCPX_ARTIFACTS_TTLhow long an artifact is kept before it is collected

autonomy

settingkinddefaultscopehotflagvariablegoverns
autonomy.maxenumrundaemonno--autonomy-maxMCPX_AUTONOMY_MAXthe most autonomy this daemon exercises, whatever a caller asks for

autostart

settingkinddefaultscopehotflagvariablegoverns
autostart.argslistdaemon,--detachedclientno--autostart-argsMCPX_AUTOSTART_ARGS(plumbing) the arguments an auto-started daemon is given
autostart.binstring(empty)clientno--autostart-binMCPX_AUTOSTART_BINwhich executable is started as the daemon
autostart.connectTimeoutduration20sclientno--autostart-connect-timeoutMCPX_AUTOSTART_CONNECT_TIMEOUThow long to wait for a started daemon to answer
autostart.idleExitduration4h0m0sclientno--autostart-idle-exitMCPX_AUTOSTART_IDLE_EXIThow long an auto-started daemon survives with nothing to do
autostart.logTailbytes2000clientno--autostart-log-tailMCPX_AUTOSTART_LOG_TAIL(plumbing) how much of the daemon log is shown when it will not start
autostart.pingTimeoutduration2sclientno--autostart-ping-timeoutMCPX_AUTOSTART_PING_TIMEOUT(plumbing) how long a health check waits before calling it dead
autostart.pollIntervalduration50msclientno--autostart-poll-intervalMCPX_AUTOSTART_POLL_INTERVAL(plumbing) how often a starting daemon is probed

catalog

settingkinddefaultscopehotflagvariablegoverns
catalog.biaslist(empty)callyes--catalog-bias, --biasMCPX_CATALOG_BIASwords that pull matching tools toward the front
catalog.budgetint2000callyes--catalog-budget, --budgetMCPX_CATALOG_BUDGETtoken ceiling for the catalog listing
catalog.instructionsbooltruecallyes--catalog-instructionsMCPX_CATALOG_INSTRUCTIONSinclude each server’s own instructions
catalog.pinnedToolslist(empty)daemonno--catalog-pinned-toolsMCPX_CATALOG_PINNED_TOOLStools always exposed in reactive mode

completion

settingkinddefaultscopehotflagvariablegoverns
completion.maxValuesint100daemonyes--completion-max-valuesMCPX_COMPLETION_MAX_VALUES(plumbing) how many completions one reply carries
completion.perMinuteint120daemonyes--completion-per-minuteMCPX_COMPLETION_PER_MINUTE(plumbing) how many completion requests one caller may make against one server per minute; 0 disables
completion.serverPerMinuteint600daemonyes--completion-server-per-minuteMCPX_COMPLETION_SERVER_PER_MINUTE(plumbing) how many completion requests one server may receive per minute across all callers; 0 disables

daemon

settingkinddefaultscopehotflagvariablegoverns
daemon.addressstring127.0.0.1daemonno--daemon-address, --addressMCPX_DAEMON_ADDRESSwhich interface the daemon listens on
daemon.autostartbooltrueclientno--daemon-autostartMCPX_DAEMON_AUTOSTARTstart the daemon on demand when it is not running
daemon.endpointstring(empty)clientno--daemon-endpointMCPX_DAEMON_ENDPOINTa daemon somewhere else, instead of the local socket
daemon.idleExitduration0sdaemonno--daemon-idle-exit, --idle-exitMCPX_DAEMON_IDLE_EXITstop the daemon after this long with nothing to do
daemon.inlineboolfalseclientno--daemon-inlineMCPX_DAEMON_INLINEas a last resort, run servers inside this process
daemon.inlineStartPollduration20msclientno--daemon-inline-start-pollMCPX_DAEMON_INLINE_START_POLL(plumbing) how often a starting in-process daemon is probed
daemon.inlineStartTimeoutduration5sclientno--daemon-inline-start-timeoutMCPX_DAEMON_INLINE_START_TIMEOUT(plumbing) how long an in-process daemon has to become reachable
daemon.leaseTTLduration30m0sdaemonyes--daemon-lease-ttlMCPX_DAEMON_LEASE_TTL(plumbing) how long a silent caller’s instances are remembered
daemon.portint0daemonno--daemon-port, --portMCPX_DAEMON_PORTlisten on a TCP port instead of choosing one
daemon.probeTimeoutduration200msclientno--daemon-probe-timeoutMCPX_DAEMON_PROBE_TIMEOUT(plumbing) how long another daemon’s socket is given to answer
daemon.reapIntervalduration30sdaemonno--daemon-reap-intervalMCPX_DAEMON_REAP_INTERVAL(plumbing) how often idle instances are swept
daemon.refreshTimeoutduration3m0sdaemonyes--daemon-refresh-timeoutMCPX_DAEMON_REFRESH_TIMEOUThow long POST /v1/refresh may take
daemon.rootspaths(empty)daemonno--daemon-rootsMCPX_DAEMON_ROOTSfilesystem paths exposed to upstream servers through roots/list
daemon.saveIntervalduration5mdaemonno--daemon-save-intervalMCPX_DAEMON_SAVE_INTERVAL(plumbing) how often daemon state is written to disk
daemon.socketProbeTimeoutduration500msdaemonno--daemon-socket-probe-timeoutMCPX_DAEMON_SOCKET_PROBE_TIMEOUT(plumbing) how long a socket left by a crashed daemon is given to answer
daemon.takeoverTimeoutduration15sdaemonno--daemon-takeover-timeoutMCPX_DAEMON_TAKEOVER_TIMEOUT(plumbing) how long a handoff may take, and how long the old daemon waits for its successor to hang up
daemon.tsnet.hostnamestring(empty)daemonno--daemon-tsnet-hostnameMCPX_DAEMON_TSNET_HOSTNAMEalso serve the daemon on a tailnet, under this name
daemon.tsnetUpTimeoutduration30sdaemonno--daemon-tsnet-up-timeoutMCPX_DAEMON_TSNET_UP_TIMEOUT(plumbing) how long a node may take to authenticate
daemon.uibooltruedaemonyes--daemon-ui, --uiMCPX_DAEMON_UIserve the dashboard page at /dashboard
daemon.uiDirstring(empty)daemonyes--daemon-ui-dir, --ui-dirMCPX_DAEMON_UI_DIRserve your own pages instead of the embedded dashboard
daemon.warmbooltruedaemonno--daemon-warm, --warmMCPX_DAEMON_WARMread every server’s schemas in the background at startup
daemon.warmTimeoutduration3m0sdaemonno--daemon-warm-timeoutMCPX_DAEMON_WARM_TIMEOUT(plumbing) how long the background schema fetch may take
daemon.watchConfigbooltruedaemonyes--daemon-watch-configMCPX_DAEMON_WATCH_CONFIGre-read the configuration files when they change on disk

deps

settingkinddefaultscopehotflagvariablegoverns
deps.autoInstallenumoffclientno--deps-auto-installMCPX_DEPS_AUTO_INSTALLwhether the runtime may fetch a missing package on its own
deps.manifestsource(empty)clientno--deps-manifestMCPX_DEPS_MANIFESTthe package.json or package.jsonc naming a script’s packages
deps.packageslist(empty)clientno--deps-packages, --packageMCPX_DEPS_PACKAGESpackages a script may import, as name or name@version
deps.sourceenumhostclientno--deps-sourceMCPX_DEPS_SOURCEwhere a script’s packages come from when nothing more specific says

diagnose

settingkinddefaultscopehotflagvariablegoverns
diagnose.historyint8daemonno--diagnose-historyMCPX_DIAGNOSE_HISTORY(plumbing) how many changes are kept per tool
diagnose.preflightbooltruecallyes--diagnose-preflightMCPX_DIAGNOSE_PREFLIGHTcheck a script’s tool calls against the live schemas first

doctor

settingkinddefaultscopehotflagvariablegoverns
doctor.timeoutduration20sclientno--doctor-timeoutMCPX_DOCTOR_TIMEOUThow long mcpx doctor gives the daemon to answer

elicit

settingkinddefaultscopehotflagvariablegoverns
elicit.askTimeoutduration45sdaemonno--elicit-ask-timeoutMCPX_ELICIT_ASK_TIMEOUThow long mcpx waits for an answer to a question it raised itself
elicit.confirmDestructiveboolfalsedaemonno--elicit-confirm-destructiveMCPX_ELICIT_CONFIRM_DESTRUCTIVEask before a call to a tool that may be destructive
elicit.disambiguateenumneverdaemonno--elicit-disambiguateMCPX_ELICIT_DISAMBIGUATEask which instance of a stateful server to use when several are live and the script did not choose
elicit.disambiguateDefaultenumnewdaemonno--elicit-disambiguate-defaultMCPX_ELICIT_DISAMBIGUATE_DEFAULTwhich instance is used when nobody answers in time: the one the scope would have chosen, or the most recently used
elicit.maxAgeduration30mdaemonno--elicit-max-ageMCPX_ELICIT_MAX_AGEthe absolute ceiling on a question’s life, however often it is read
elicit.pendingLimitint100daemonyes--elicit-pending-limitMCPX_ELICIT_PENDING_LIMIT(plumbing) how many unanswered questions one listing returns
elicit.ttlduration120sdaemonno--elicit-ttlMCPX_ELICIT_TTLhow long a question waits for an answer, renewed by use

embeddings

settingkinddefaultscopehotflagvariablegoverns
embeddings.apiKeystring(empty)daemonyes--embeddings-api-keyMCPX_EMBEDDINGS_API_KEYAPI key for remote embeddings endpoint
embeddings.backendstringautodaemonyes--embeddings-backendMCPX_EMBEDDINGS_BACKENDwhich embedding engine to use (auto, local, remote, wasm)
embeddings.modelstringtext-embedding-3-smalldaemonyes--embeddings-modelMCPX_EMBEDDINGS_MODELmodel name for remote embeddings endpoint
embeddings.urlstring(empty)daemonyes--embeddings-urlMCPX_EMBEDDINGS_URLOpenAI-compatible embeddings endpoint URL
embeddings.wasmPathstring(empty)daemonyes--embeddings-wasm-pathMCPX_EMBEDDINGS_WASM_PATHpath to compiled WebAssembly neural embedding model

events

settingkinddefaultscopehotflagvariablegoverns
events.historyint1024daemonno--events-historyMCPX_EVENTS_HISTORYhow many past events a late subscriber can replay
events.reconnectduration2sclientno--events-reconnectMCPX_EVENTS_RECONNECThow long a client waits before resuming a dropped stream

exec

settingkinddefaultscopehotflagvariablegoverns
exec.maxOutputBytesbytes64KiBcallyes--exec-max-output-bytesMCPX_EXEC_MAX_OUTPUT_BYTEScap the stdout a run returns, with a truncation marker
exec.outputenumtextcallyes--exec-output, --outputMCPX_EXEC_OUTPUTtext for a terminal, structured for one document, stream for frames
exec.timeoutduration120scallyes--exec-timeoutMCPX_EXEC_TIMEOUTkill a script after this long
exec.whereenumautocallyes--exec-whereMCPX_EXEC_WHEREin this process, or on the daemon

hooks

settingkinddefaultscopehotflagvariablegoverns
hooksstring(empty)daemonno--hooksMCPX_HOOKSconfig-declared hooks over the event bus: a JSON array of hook objects
hooks.autonomyenumruncallyes--hooks-autonomyMCPX_HOOKS_AUTONOMYwhether configured script hooks (before, onSuccess, onError) run
hooks.timeoutduration2sdaemonno--hooks-timeoutMCPX_HOOKS_TIMEOUTthe hard timeout for a declared hook that does not set its own

http

settingkinddefaultscopehotflagvariablegoverns
http.bodyLimitbytes1048576daemonyes--http-body-limitMCPX_HTTP_BODY_LIMIT(plumbing) the ceiling on an ordinary /v1 request body
http.callBodyLimitbytes67108864daemonyes--http-call-body-limitMCPX_HTTP_CALL_BODY_LIMITthe ceiling on a POST /v1/call body
http.controlBodyLimitbytes65536daemonyes--http-control-body-limitMCPX_HTTP_CONTROL_BODY_LIMIT(plumbing) the ceiling on a body that should hold one field
http.idleConnTimeoutduration30sclientno--http-idle-conn-timeoutMCPX_HTTP_IDLE_CONN_TIMEOUT(plumbing) how long an unused connection is kept
http.idleConnsint8clientno--http-idle-connsMCPX_HTTP_IDLE_CONNS(plumbing) idle connections kept to a local daemon
http.readHeaderTimeoutduration10sdaemonno--http-read-header-timeoutMCPX_HTTP_READ_HEADER_TIMEOUT(plumbing) how long a client has to finish sending its headers
http.remoteIdleConnsint16clientno--http-remote-idle-connsMCPX_HTTP_REMOTE_IDLE_CONNS(plumbing) idle connections kept to a daemon over the network
http.requestTimeoutduration10m0sclientno--http-request-timeoutMCPX_HTTP_REQUEST_TIMEOUThow long a CLI request to the daemon may take
http.shutdownGraceduration5sdaemonyes--http-shutdown-graceMCPX_HTTP_SHUTDOWN_GRACEhow long in-flight requests have when the daemon stops
http.ssePingduration15sdaemonyes--http-sse-pingMCPX_HTTP_SSE_PING(plumbing) how often a comment is sent on an idle event stream
http.sseRetryduration2sdaemonyes--http-sse-retryMCPX_HTTP_SSE_RETRY(plumbing) how long a dropped subscriber is told to wait
http.streamBufferInitbytes65536clientno--http-stream-buffer-initMCPX_HTTP_STREAM_BUFFER_INIT(plumbing) the initial line buffer when reading an event stream
http.streamBufferMaxbytes8388608clientno--http-stream-buffer-maxMCPX_HTTP_STREAM_BUFFER_MAX(plumbing) the largest single event line that will be read

logging

settingkinddefaultscopehotflagvariablegoverns
logging.dirstring(empty)daemonno--logging-dir, --log-dirMCPX_LOGGING_DIRwhere the JSONL files are written
logging.filebooltruedaemonno--logging-fileMCPX_LOGGING_FILEwhether the durable JSONL log is written at all
logging.formatenumtextclientno--logging-format, --formatMCPX_LOGGING_FORMAThow records are rendered
logging.includelisthost,user,process,versiondaemonno--logging-include, --includeMCPX_LOGGING_INCLUDEwhich context blocks are attached to lifecycle records
logging.keepint8daemonno--logging-keep, --keepMCPX_LOGGING_KEEPhow many rolled files to keep
logging.levelenuminfodaemonyes--logging-level, --log-levelMCPX_LOGGING_LEVEL, MCPX_LOG_LEVELthe lowest level that is kept
logging.maxAgeduration24hdaemonno--logging-max-ageMCPX_LOGGING_MAX_AGEroll the log file once it is this old
logging.maxBytesbytes16MBdaemonno--logging-max-bytesMCPX_LOGGING_MAX_BYTESroll the log file once it reaches this size
logging.maxLinesint0daemonno--logging-max-linesMCPX_LOGGING_MAX_LINESroll the log file once it holds this many records
logging.sourceenumwarndaemonyes--logging-source, --log-sourceMCPX_LOGGING_SOURCEfrom which level upward to record the calling file and line
logging.traceboolfalsedaemonno--logging-traceMCPX_TRACElog which instance served every tool call

logstore

settingkinddefaultscopehotflagvariablegoverns
logstore.followBacklogint1000clientno--logstore-follow-backlogMCPX_LOGSTORE_FOLLOW_BACKLOG(plumbing) how many records one poll of mcpx log --follow may emit
logstore.queryLimitint100callyes--logstore-query-limitMCPX_LOGSTORE_QUERY_LIMIThow many records a log query returns by default

mcp

settingkinddefaultscopehotflagvariablegoverns
mcp.pageSizeint100clientno--mcp-page-sizeMCPX_MCP_PAGE_SIZEhow many items one tools/list reply carries
mcp.passthroughstring(empty)clientno--mcp-passthrough, --passthroughMCPX_MCP_PASSTHROUGHserve upstreams’ tools, prompts and resources under their own names

model

settingkinddefaultscopehotflagvariablegoverns
model.apiKeyEnvstring(empty)daemonno--model-api-key-envMCPX_MODEL_API_KEY_ENVthe environment variable holding the configured provider’s API key
model.baseURLstring(empty)daemonno--model-base-urlMCPX_MODEL_BASE_URLthe OpenAI-compatible endpoint the configured provider calls
model.budgetint0daemonno--model-budgetMCPX_MODEL_BUDGEThard ceiling on the tokens one generation through the layer may ask for; 0 is no ceiling
model.defaultstring(empty)daemonno--model-defaultMCPX_MODEL_DEFAULTthe model id used when an operation names none, as provider/model
model.providerenumnonedaemonno--model-providerMCPX_MODEL_PROVIDERwhere mcpx’s own generation gets a model: a configured provider, sampling through the caller, or none
model.timeoutduration1m30sdaemonno--model-timeoutMCPX_MODEL_TIMEOUThow long one generation through the layer may take

output

settingkinddefaultscopehotflagvariablegoverns
output.colorenumautoclientno--output-colorMCPX_OUTPUT_COLORwhether to colourise the browser
output.jsonboolfalseclientno--output-json, --jsonMCPX_OUTPUT_JSONemit one machine-readable document

paths

settingkinddefaultscopehotflagvariablegoverns
paths.adapterspaths(empty)clientno--paths-adaptersMCPX_PATHS_ADAPTERSfiles declaring command-line programs as MCP servers
paths.apispaths(empty)clientno--paths-apisMCPX_PATHS_APISfiles naming OpenAPI documents to expose as tools
paths.cachestring(empty)clientno--paths-cacheMCPX_PATHS_CACHE, MCPX_CACHE_DIRwhere generated clients and schemas are kept
paths.configpaths(empty)clientno--paths-configMCPX_PATHS_CONFIGwhere configuration files are looked for
paths.configFilestring(empty)clientno--configMCPX_CONFIGread exactly this configuration file instead of searching
paths.depspaths(empty)clientno--paths-depsMCPX_PATHS_DEPSwhere a script’s package.json / package.jsonc is looked for
paths.placeholderspaths(empty)clientno--paths-placeholdersMCPX_PATHS_PLACEHOLDERSdirectories of files declaring launcher placeholders
paths.pluginspaths(empty)clientno--paths-pluginsMCPX_PATHS_PLUGINSdirectories of plugins: an mcpx-plugin.json manifest, or executables loaded as zero-manifest plugins
paths.scriptspaths(empty)clientno--paths-scriptsMCPX_PATHS_SCRIPTSwhere named scripts are looked for
paths.statestring(empty)clientno--paths-stateMCPX_PATHS_STATE, MCPX_STATE_DIRwhere the daemon socket, logs and index live

plugin

settingkinddefaultscopehotflagvariablegoverns
plugin.annotatebooltruepluginno--plugin-annotateMCPX_PLUGIN_ANNOTATEadd mcpx’s own notes to a tool result
plugin.backendenumautopluginno--plugin-backendMCPX_PLUGIN_BACKENDwhether the plugin talks to the daemon or spawns the binary
plugin.binstringmcpxpluginno--plugin-binMCPX_PLUGIN_BINthe executable the plugin invokes
plugin.binArgslist(empty)pluginno--plugin-bin-argsMCPX_PLUGIN_BIN_ARGSarguments put in front of every plugin invocation of mcpx
plugin.daemonToolsenumautopluginno--plugin-daemon-toolsMCPX_PLUGIN_DAEMON_TOOLSoffer the tools that list and choose between daemons
plugin.discoveryRetryduration1m0spluginno--plugin-discovery-retryMCPX_PLUGIN_DISCOVERY_RETRYhow long the plugin waits before looking for mcpx again
plugin.envenumfullpluginno--plugin-envMCPX_PLUGIN_ENVhow much session context is put into each command’s environment
plugin.headlessenumautopluginno--plugin-headlessMCPX_PLUGIN_HEADLESSsuppress toasts, because nobody is looking at a screen
plugin.instructionsboolfalsepluginno--plugin-instructionsMCPX_PLUGIN_INSTRUCTIONSadd mcpx usage guidance to the system prompt
plugin.rememberenumsessionpluginno--plugin-rememberMCPX_PLUGIN_REMEMBERhow long a chosen daemon stays chosen
plugin.toolTimingboolfalsepluginno--plugin-tool-timingMCPX_PLUGIN_TOOL_TIMINGwrite every tool outcome into mcpx’s log
plugin.toolsboolfalsepluginno--plugin-toolsMCPX_PLUGIN_TOOLSexpose mcpx itself as tools the model can call

plumbing

settingkinddefaultscopehotflagvariablegoverns
plumbing.allowTsJsOverlapboolfalseclientno--plumbing-allow-ts-js-overlapMCPX_PLUMBING_ALLOW_TS_JS_OVERLAP(plumbing) permit a script directory holding both foo.ts and foo.js
plumbing.indexOnQuerybooltruedaemonno--plumbing-index-on-queryMCPX_PLUMBING_INDEX_ON_QUERY(plumbing) bring the log index up to date before answering a query
plumbing.launcherPlaceholderRepeatlist(empty)clientno--plumbing-launcher-placeholder-repeatMCPX_PLUMBING_LAUNCHER_PLACEHOLDER_REPEAT(plumbing) launcher placeholders permitted to resolve more than once
plumbing.sourceDirAllowedbooltrueclientno--plumbing-source-dir-allowedMCPX_PLUMBING_SOURCE_DIR_ALLOWED(plumbing) whether a directory may stand in for a source string at all
plumbing.sourceDirRecursiveboolfalseclientno--plumbing-source-dir-recursiveMCPX_PLUMBING_SOURCE_DIR_RECURSIVE(plumbing) when a directory is given as source, descend into subdirectories
plumbing.sourceProbePathsbooltrueclientno--plumbing-source-probe-pathsMCPX_PLUMBING_SOURCE_PROBE_PATHS(plumbing) treat a source argument that names an existing file as a file
plumbing.strictUnknownKeysboolfalseclientno--plumbing-strict-unknown-keysMCPX_PLUMBING_STRICT_UNKNOWN_KEYS(plumbing) fail on a configuration key no setting claims
plumbing.validatePathsbooltrueclientno--plumbing-validate-pathsMCPX_PLUMBING_VALIDATE_PATHS(plumbing) resolve and verify every referenced path before doing any work

policy

settingkinddefaultscopehotflagvariablegoverns
policy.writeenumaskdaemonno--policy-writeMCPX_POLICY_WRITEwhat to do before a call that may write: ask for confirmation, allow it, or deny it

pool

settingkinddefaultscopehotflagvariablegoverns
pool.callTimeoutduration120sdaemonyes--pool-call-timeoutMCPX_POOL_CALL_TIMEOUThow long one tool call may take
pool.idleTimeoutduration5mdaemonyes--pool-idle-timeoutMCPX_POOL_IDLE_TIMEOUThow long an unused server lingers before it is stopped
pool.maxint4daemonyes--pool-maxMCPX_POOL_MAXhow many copies of one server may run at once
pool.minint0daemonyes--pool-minMCPX_POOL_MINhow many copies to keep started even when idle
pool.scopeenumglobaldaemonno--pool-scopeMCPX_POOL_SCOPEwhat counts as the same caller for sharing purposes
pool.sharingenumshareddaemonno--pool-sharingMCPX_POOL_SHARINGwhether callers reuse one instance or each get their own
pool.startTimeoutduration60sdaemonyes--pool-start-timeoutMCPX_POOL_START_TIMEOUThow long a server has to become ready

preset

settingkinddefaultscopehotflagvariablegoverns
presetlist(empty)clientno--presetMCPX_PRESETwhich presets to apply, in order

presets

settingkinddefaultscopehotflagvariablegoverns
presetsstring(empty)clientno--presetsMCPX_PRESETSnamed flag bundles: {name: [“–json”, “–timeout=30s”]}

prompt

settingkinddefaultscopehotflagvariablegoverns
prompt.autonomyenumproposecallyes--prompt-autonomyMCPX_PROMPT_AUTONOMYwhether a request returns the script (propose) or runs it (run)
prompt.catalogBudgetint1200callyes--prompt-catalog-budgetMCPX_PROMPT_CATALOG_BUDGETapproximate token ceiling for the slice of catalog sent with a sampling request
prompt.maxTokensint1500callyes--prompt-max-tokensMCPX_PROMPT_MAX_TOKENS(plumbing) maxTokens on the sampling request
prompt.runTimeoutduration5m0scallyes--prompt-run-timeoutMCPX_PROMPT_RUN_TIMEOUThow long a recipe or generated script may run
prompt.sampleenumneverdaemonno--prompt-sampleMCPX_PROMPT_SAMPLEwhether a request with no matching recipe may ask the caller’s model to write one
prompt.sampleTimeoutduration1m30sdaemonno--prompt-sample-timeoutMCPX_PROMPT_SAMPLE_TIMEOUThow long a generation request waits for a model

proto

settingkinddefaultscopehotflagvariablegoverns
proto.askPollduration500msdaemonno--proto-ask-pollMCPX_PROTO_ASK_POLL(plumbing) how long one wait for a question may block
proto.askRoundsint8daemonno--proto-ask-roundsMCPX_PROTO_ASK_ROUNDShow many times one request may come back asking for more
proto.askTTLduration15mdaemonno--proto-ask-ttlMCPX_PROTO_ASK_TTL(plumbing) how long the daemon keeps a call waiting for an answer
proto.askTimeoutduration10mdaemonno--proto-ask-timeoutMCPX_PROTO_ASK_TIMEOUThow long one request may be held while a question goes unanswered
proto.elicitationPerMinuteint30daemonno--proto-elicitation-per-minuteMCPX_PROTO_ELICITATION_PER_MINUTEmax elicitation requests one upstream server may make per minute, across all its sessions
proto.mcpPathstring/mcpdaemonno--proto-mcp-pathMCPX_PROTO_MCP_PATHwhere the daemon serves MCP
proto.nativebooltruedaemonno--proto-nativeMCPX_PROTO_NATIVEput an upstream server’s questions to mcpx’s own MCP client
proto.samplingPerMinuteint60daemonno--proto-sampling-per-minuteMCPX_PROTO_SAMPLING_PER_MINUTEmax sampling requests one upstream server may make per minute, across all its sessions
proto.samplingSessionPerMinuteint20daemonno--proto-sampling-session-per-minuteMCPX_PROTO_SAMPLING_SESSION_PER_MINUTEmax sampling requests one upstream server may make within a single session per minute
proto.serveMCPbooltruedaemonno--proto-serve-mcpMCPX_PROTO_SERVE_MCPmount mcpx’s own MCP server on the daemon’s listeners
proto.sessionIdleduration30mdaemonno--proto-session-idleMCPX_PROTO_SESSION_IDLE(plumbing) how long an unused Streamable HTTP session is kept
proto.stateTTLduration30mdaemonno--proto-state-ttlMCPX_PROTO_STATE_TTL(plumbing) how long a client may resume an interrupted request with

protoMessages

settingkinddefaultscopehotflagvariablegoverns
protoMessages.listMaxAgeduration1m0sdaemonno--proto-messages-list-max-ageMCPX_PROTO_MESSAGES_LIST_MAX_AGEttlMs on server/discover and every list result sent to a 2026-07-28 client
protoMessages.readMaxAgeduration0sdaemonno--proto-messages-read-max-ageMCPX_PROTO_MESSAGES_READ_MAX_AGEttlMs on resources/read results sent to a 2026-07-28 client
protoMessages.taskAfterduration2sdaemonno--proto-messages-task-afterMCPX_PROTO_MESSAGES_TASK_AFTERhow long a tools/call runs in line before a tasks-extension client is handed a task

protoTasks

settingkinddefaultscopehotflagvariablegoverns
protoTasks.cancelGraceduration5sdaemonno--proto-tasks-cancel-graceMCPX_PROTO_TASKS_CANCEL_GRACEhow long cooperative task work may finish after cancellation
protoTasks.maxPerRequestorint32daemonno--proto-tasks-max-per-requestorMCPX_PROTO_TASKS_MAX_PER_REQUESTORhow many non-terminal tasks one requestor may hold at once
protoTasks.maxTTLduration1h0m0sdaemonno--proto-tasks-max-ttlMCPX_PROTO_TASKS_MAX_TTLthe longest TTL a task may ask for; longer requests are clamped
protoTasks.perMinuteint120daemonno--proto-tasks-per-minuteMCPX_PROTO_TASKS_PER_MINUTEthe tasks/* admission window per requestor; 0 disables it
protoTasks.pollIntervalduration1sdaemonno--proto-tasks-poll-intervalMCPX_PROTO_TASKS_POLL_INTERVALthe pollInterval every task carries

recipes

settingkinddefaultscopehotflagvariablegoverns
recipes.limitint5callyes--recipes-limitMCPX_RECIPES_LIMIThow many ranked recipes are returned
recipes.matchMarginint150callyes--recipes-match-marginMCPX_RECIPES_MATCH_MARGINhow far ahead the best recipe must be, as a percentage of the next one
recipes.minScoreint30callyes--recipes-min-scoreMCPX_RECIPES_MIN_SCOREthe score below which a request is not considered a match for any recipe

registry

settingkinddefaultscopehotflagvariablegoverns
registry.limitint20callyes--registry-limitMCPX_REGISTRY_LIMIThow many registry results are returned
registry.maxPagesint10clientno--registry-max-pagesMCPX_REGISTRY_MAX_PAGES(plumbing) how many requests one registry search may make
registry.pageSizeint30clientno--registry-page-sizeMCPX_REGISTRY_PAGE_SIZE(plumbing) how many entries are fetched per registry request
registry.timeoutduration30sclientno--registry-timeoutMCPX_REGISTRY_TIMEOUThow long a registry request may take
registry.urlstringhttps://registry.modelcontextprotocol.ioclientno--registry-urlMCPX_REGISTRY_URLwhere mcpx registry looks for servers

repair

settingkinddefaultscopehotflagvariablegoverns
repair.autonomyenumadvisecallyes--repair-autonomyMCPX_REPAIR_AUTONOMYhow far mcpx goes about a failed call: off attaches nothing, advise attaches diagnostics

retention

settingkinddefaultscopehotflagvariablegoverns
retention.maxToolsint24daemonno--retention-max-toolsMCPX_RETENTION_MAX_TOOLSmaximum active tools in reactive working set
retention.stickyWindowduration2hdaemonno--retention-sticky-windowMCPX_RETENTION_STICKY_WINDOWduration explicitly requested tools remain retained

script

settingkinddefaultscopehotflagvariablegoverns
script.beforesource(empty)clientno--script-beforeMCPX_SCRIPT_BEFOREruns first, ahead of the globals being installed
script.captureConsolebooltrueclientno--script-capture-consoleMCPX_SCRIPT_CAPTURE_CONSOLEroute console calls into the log
script.cwdenumcallerclientno--script-cwdMCPX_SCRIPT_CWDwhere a script runs: caller, script, config or workdir
script.envlist(empty)clientno--script-env, --envMCPX_SCRIPT_ENVKEY=VALUE pairs added to the script’s environment
script.importsenumreportclientno--script-importsMCPX_SCRIPT_IMPORTSresolve the script’s import graph before running it
script.importsOnDynamicenumwarnclientno--script-imports-on-dynamicMCPX_SCRIPT_IMPORTS_ON_DYNAMICwhat to do about an import() whose specifier is computed
script.interpreterstring(empty)clientno--script-interpreter, --interpreter, --shellMCPX_SCRIPT_INTERPRETERcommand line that runs scripts instead of a JavaScript runtime
script.launchersource(empty)clientno--script-launcherMCPX_SCRIPT_LAUNCHERreplace the generated launcher entirely
script.onErrorsource(empty)clientno--script-on-errorMCPX_SCRIPT_ON_ERRORruns when the script throws; the error still propagates
script.onSuccesssource(empty)clientno--script-on-successMCPX_SCRIPT_ON_SUCCESSruns when the script returns without throwing
script.permissionsstringallclientno--script-permissions, --permissionsMCPX_SCRIPT_PERMISSIONS, MCPX_PERMISSIONSpermission profiles, composed in order, or raw: flags
script.prefixsource(empty)clientno--script-prefixMCPX_SCRIPT_PREFIXruns after globals are installed, before the script
script.profilesstring(empty)clientno--script-profilesMCPX_SCRIPT_PROFILESuser profiles: {name: {deno: [flags], node: [flags]}} or {name: “raw flags”}
script.runtimestringautoclientno--script-runtime, --runtimeMCPX_SCRIPT_RUNTIMEwhich JavaScript runtime executes the script
script.runtimeOrderlistdeno,bun,nodeclientno--script-runtime-orderMCPX_SCRIPT_RUNTIME_ORDERthe runtimes auto tries, in order
script.runtimesstring(empty)clientno--script-runtimesMCPX_SCRIPT_RUNTIMESnamed runtime binaries: {name: {kind, bin, args}}
script.suffixsource(empty)clientno--script-suffixMCPX_SCRIPT_SUFFIXruns last on both paths, like a finally
script.typecheckenumoffclientno--script-typecheckMCPX_SCRIPT_TYPECHECKcheck the generated program before running it
settingkinddefaultscopehotflagvariablegoverns
search.limitint20callyes--search-limit, --limit, --nMCPX_SEARCH_LIMIThow many tools a search returns
search.semanticboolfalsecallyes--search-semanticMCPX_SEARCH_SEMANTICrank search results using local dense vector embeddings

serve

settingkinddefaultscopehotflagvariablegoverns
serve.modestringcode-modedaemonno--serve-mode, --modeMCPX_SERVE_MODEserver mode: code-mode, reactive, or full

session

settingkinddefaultscopehotflagvariablegoverns
session.releaseTimeoutduration10sclientno--session-release-timeoutMCPX_SESSION_RELEASE_TIMEOUT(plumbing) how long releasing a finished session may take

spec

settingkinddefaultscopehotflagvariablegoverns
spec.firststring(empty)daemonno--mcp-specMCPX_MCP_SPECmove one MCP revision to the front of spec.precedence
spec.lenientlist(empty)daemonno--spec-lenientMCPX_SPEC_LENIENTMCP revisions not held strictly; every revision not listed is strict
spec.precedencelist2026-07-28,2025-11-25,2025-06-18,2025-03-26,2024-11-05daemonno--spec-precedenceMCPX_SPEC_PRECEDENCEMCP revisions in the order their rules win a conflict, first wins

stats

settingkinddefaultscopehotflagvariablegoverns
stats.topint20callyes--stats-topMCPX_STATS_TOPhow many rows a ranked statistic shows

tasks

settingkinddefaultscopehotflagvariablegoverns
tasks.resultWaitduration2m0scallyes--tasks-result-waitMCPX_TASKS_RESULT_WAIThow long collecting a task result blocks before giving up
tasks.ttlduration10m0sdaemonyes--tasks-ttlMCPX_TASKS_TTLhow long a finished task’s result is kept

transport

settingkinddefaultscopehotflagvariablegoverns
transport.allowedOriginslist(empty)daemonno--transport-allowed-originsMCPX_TRANSPORT_ALLOWED_ORIGINSbrowser Origins, beyond loopback, that may reach /mcp
transport.bearerTokenstring(empty)daemonno--transport-bearer-tokenMCPX_TRANSPORT_BEARER_TOKENwhen set, every /v1 and /mcp request must carry this token as a Bearer credential
transport.sessionMetaKeyslistdev.mcpx/session,ai.opencode/sessionIDdaemonyes--transport-session-meta-keysMCPX_TRANSPORT_SESSION_META_KEYSthe _meta keys a client may name its session with, first present wins
transport.sseKeepAliveduration15sdaemonno--transport-sse-keep-aliveMCPX_TRANSPORT_SSE_KEEP_ALIVEhow often a quiet MCP event stream carries a comment line
transport.stdioDrainduration30sclientno--transport-stdio-drainMCPX_TRANSPORT_STDIO_DRAINhow long mcpx serve finishes in-flight requests after its input closes

upstream

settingkinddefaultscopehotflagvariablegoverns
upstream.eraCachebooltruedaemonno--upstream-era-cacheMCPX_UPSTREAM_ERA_CACHEremember which era each server configuration speaks, across restarts
upstream.probeTimeoutduration2sdaemonno--upstream-probe-timeoutMCPX_UPSTREAM_PROBE_TIMEOUThow long a stdio server/discover may go unanswered before initialize is also sent
upstream.protocolenumprefer-discoverdaemonno--upstream-protocolMCPX_UPSTREAM_PROTOCOLwhich request to send first to a server that names no protocol of its own; one of prefer-discover (also modern, prefer-modern, prefer-stateless, prefer-newest), prefer-initialize (also legacy, prefer-legacy, prefer-session, prefer-oldest), force-discover (also force-modern, force-stateless), force-initialize (also force-legacy, force-session), follow

Adapter declarations

An entry in a paths.adapters file names a program and the subcommands worth exposing. command is normally a binary on PATH; runtime makes it a script instead, and resolves the interpreter for you:

{ "adapters": [{
  "name": "reports", "command": "./tools/report.py", "runtime": "py",
  "tools": [{ "name": "daily", "params": [{ "name": "day", "required": true }] }]
}] }

runtime is bash, js, ts or py. bash resolves bash (then sh), py resolves python3 (then python), and js/ts use the same deno/bun/node order script.runtimeOrder chooses for mcpx exec. This is a separate resolver on purpose: script.runtime names the runtime mcpx itself executes generated scripts on, not the one a declaration asks for. A relative command resolves against the declaration’s dir, then PATH. Without runtime, nothing changes: command is looked up on PATH and exec’d directly. mcpx adapter check reports a missing interpreter or script.

mcpx catalog also ends with the recipe tools in tools/list: how many are exposed against the recipe budget, and which recipes were kept out by a name collision or the cap.