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

The script environment

Settings are what a person tells mcpx. This page is the other direction: what mcpx, and the opencode plugin, tell a program they start.

A script reads its session from here. The generated client finds the daemon from here – the endpoint, the socket, whether the daemon can see this filesystem. An mcpx command run inside a script, or inside a shell the editor opened, learns which session it belongs to from here, which is what lets a session-scoped server give each agent its own browser instead of sharing one. It is an interface with as many consumers as the settings have.

Until #172 it was declared nowhere. The only list was the code that happened to set each name, spread across five packages and a TypeScript file, and the documentation had already drifted from it: it said the plugin sets MCPX_SESSION and MCPX_CALL. The plugin sets MCPX_SESSION_ID and MCPX_CALL_ID; MCPX_SESSION is a different variable, set by mcpx for its own scripts; and MCPX_CALL has never existed. Two variables, one renamed in somebody’s head, is exactly the drift a hand-written list produces.

How it is kept true

The table below is generated from settings.ScriptEnv() in internal/settings/scriptenv.go, and tests hold that table to the code:

  • Everything set is declared, by the right setter. The Go sources are walked as syntax – a map key, an assignment to an index, a KEY=VALUE string, os.Setenv – and the plugin’s put(e, "MCPX_...") calls are read, and every name found must be in the table with the part of mcpx that set it listed. A setter the table names that sets nothing fails too.
  • Everything read is declared. A lookup of an MCPX_ variable anywhere outside internal/settings must be either a setting read through the resolved settings, a variable in this table, or one of four lookups on an allowlist that carries the reason for each – three happen before any settings exist, the fourth where there is no App to ask. An entry nothing needs fails the test. A variable the registry owns may not be read by name even when it is also here, because that would ignore the config file and the flag.
  • The “read by” column is checked. If it says the generated client reads a variable, the generated TypeScript does; if the plugin reads one, the column says so.
  • This page is current. The test regenerates the table and fails when it differs.

This table is what mcpx adds, not all a child gets

Every one of these is added to the environment mcpx itself was started with. A child gets the parent’s environment entire, and then these on top — unless its shebang replaces part of it: env -i starts the child from an empty environment, and env -u NAME removes just that name (both then get these on top). Both the dispatched path and the JavaScript path apply what the shebang asked for:

  • internal/runner/runner.go:359 — cmd.Env = append(os.Environ(), …), every script the runner starts
  • internal/adapter/adapter.go:239 — cmd.Env = os.Environ()
  • internal/mcpclient/stdio.go:66 — env = append(env, os.Environ()...), under InheritEnv, for every stdio MCP server the pool starts

So a script also sees SSH_AUTH_SOCK, GITHUB_TOKEN, AWS_*, and whatever else was in the shell that started the daemon. PATH is inherited rather than constructed, which means a daemon started from a shell with an unusual PATH passes it on.

script.permissions does not change this. It sandboxes the filesystem and the network through the runtime’s own flags; there is no allowlist or denylist for the environment, and nothing here is filtered.

This is worth knowing before putting a secret in the environment of a shell you then run mcpx serve from. It is stated rather than fixed because the alternative — an allowlist — would break every server that expects its own API key to arrive this way, which is most of them.

Configuration is not here

A variable here is a fact about one run – which session, which daemon, which file – handed from a parent to a child. A knob is a setting, with a configuration key, a flag, a /v1/settings row and an MCP tool; those are in configuration.md. Two names are both: MCPX_LOG_LEVEL and MCPX_DAEMON_ENDPOINT are written for a child, and a child that is mcpx reads each as the setting it names.

Some variables are read by mcpx and are neither: which configuration file to load (MCPX_CONFIG, the setting paths.configFile) and where the state lives (MCPX_STATE_DIR, MCPX_CACHE_DIR) are needed before there is a configuration to resolve settings from. They are settings, marked as such, and read by name for that reason.

The variables

variableset bywhenwhat it isread by
MCPX_SESSIONmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec); an event consumeralwaysthe session key this run’s tool calls are leased under: –session, else MCPX_SESSION_ID, else one minted for the runthe generated client, which sends it with every call
MCPX_SESSION_IDmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec); an event consumer; the opencode plugin; the plugin loader, for a discovered pluginthe CLI passes through the value it was given, which may be empty; the plugin sets it at every plugin.env levelthe session the host assigned. Unlike MCPX_SESSION it survives from one command to the next, so it is what session-scoped servers key onmcpx run inside the script or shell, as its session when –session is not given
MCPX_PARENT_SESSION_IDmcpx exec / mcpx run; the opencode pluginthe CLI passes through what it was given; the plugin at plugin.env=full, when there is a parentthe session that spawned this one, so a parent agent and its subagents can share a servermcpx run inside the script or shell, and the generated client for parent-session scoping
MCPX_EPHEMERALmcpx exec / mcpx runalways1 when the session key was minted for this run alone, so its instances may be stopped as soon as it endsthe generated client
MCPX_RUNmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec)alwaysthis execution’s run id, which groups the artifacts it producesthe generated client, when storing an artifact
MCPX_INPUTmcpx exec / mcpx runmcpx exec and mcpx run, locally and with –remote; not when mcpx serve collects the outputreport: nothing in this run can answer a server’s question mid-call, so a call that asks one exits 75 with the question instead of waiting for it to expirethe generated client, sent as X-Mcpx-Input
MCPX_HOOK_NAMEthe declared-hook runner, for a hook it runsalwaysthe declared name of the hook being runthe hook itself
MCPX_HOOK_PHASEthe declared-hook runner, for a hook it runs; the exec service (/v1/exec, mcpx_exec)alwaysthe hook’s phase; always observe for config-declared hooks, the exec hook’s phase (pre, post, onError, onSuccess) for a per-call exec hookthe hook itself
MCPX_HOOK_POINTthe exec service (/v1/exec, mcpx_exec)alwaysthe point the hook is attached to; script for per-call exec hooks in this versionthe hook itself
MCPX_HOOK_ERRORthe exec service (/v1/exec, mcpx_exec)only for a hook running because the script failed; truncated to 1024 bytesthe error the hook is running for, in one line; the full hook context is JSON on stdinthe hook itself
MCPX_HOOK_EVENT_KINDthe declared-hook runner, for a hook it runsalwaysthe kind of the event the hook is running for; the full event is JSON on stdinthe hook itself
MCPX_HOOK_EVENT_SEQthe declared-hook runner, for a hook it runsalwaysthe event’s sequence number on the daemon’s busthe hook itself
MCPX_HOOK_EVENT_SERVERthe declared-hook runner, for a hook it runsempty when the event names no serverthe server the event is aboutthe hook itself
MCPX_HOOK_EVENT_SESSIONthe declared-hook runner, for a hook it runsempty when the event belongs to no sessionthe session the event belongs tothe hook itself
MCPX_HOOK_EVENT_URIthe declared-hook runner, for a hook it runsempty when the event names no resourcethe resource URI the event is aboutthe hook itself
MCPX_ENDPOINTmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec); an event consumeralwaysthe daemon’s HTTP endpointthe generated client; the plugin as a fallback for MCPX_DAEMON_ENDPOINT
MCPX_SOCKETmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec); the plugin loader, for a discovered pluginempty when the daemon is remote, since its socket is not reachable from herethe daemon’s unix socket, which is faster than the endpoint. Also where a daemon binds and a client dials, for anyone who sets it by handthe generated client, mcpx run inside the script or shell, the plugin
MCPX_TRANSPORT_BEARER_TOKENmcpx exec / mcpx runonly when transport.bearerToken is configuredthe daemon’s bearer token, handed to the script so the generated client can authenticate to a daemon that requires it (#1975); the token itself stays out of the generated codethe generated client, mcpx run inside the script or shell, the plugin; also the setting transport.bearerToken
MCPX_SETTINGSmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec)only when the caller sent call settings with the run (for example a requested pool.callTimeout)the call-settings header for this run, carried into the script so the generated client forwards the caller’s per-call requests on the calls it makes on the script’s behalfthe generated client, which sends it as the x-mcpx-settings header
MCPX_ARTIFACTS_LOCALmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec)always1 when the daemon shares this filesystem, so artifact({path}) may hand over a path instead of the bytesthe generated client
MCPX_CWDmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec)the exec service only when the caller sent its working directorythe directory the caller ran mcpx from, which a remote run cannot discoverthe generated client (paths.cwd, and cwd scoping)
MCPX_PIDmcpx exec / mcpx run; the exec service (/v1/exec, mcpx_exec)alwaysthe pid of the mcpx process that started the scriptthe generated client, for pid scoping
MCPX_ENTRYthe runner, for every scriptalwaysthe file the runtime was started on: mcpx’s generated launcher, or the script itself under –no-launcher. For exec the script is a generated filethe generated client (paths.entry)
MCPX_CLIENTthe runner, for every scriptalwaysthe generated client modulethe generated client (paths.client)
MCPX_CONFIG_PATHmcpx exec / mcpx runalwaysthe nearest configuration file in effect, empty when there is nonethe generated client (paths.config)
MCPX_SCRIPT_DIRSmcpx exec / mcpx runalwaysthe directories searched for named scripts, nearest first, colon-separatedthe generated client (paths.scriptDirs)
MCPX_API_DECLARATION_SHA256mcpx exec / mcpx runalwaysthe fingerprint of a declared API’s declaration, set on the child a declared API launches: PoolID hashes the environment, so editing the declaration replaces the pool instead of keeping a child started for the old onethe pool, as a pool identity input (api serve: PoolID hashes the server environment)
MCPX_RUNTIMEthe runner, for every scriptalwaysdeno, bun or node; on a dispatched run, the interpreter’s name (sh, python3)the generated client; on a dispatched run, the script itself
MCPX_DISPATCHthe runner, for every scriptonly on a dispatched (non-JavaScript) runhow the interpreter was chosen: shebang or interpreter (script.interpreter)the script itself, and anyone reading its environment
MCPX_ALLOW_READthe runner, for every scriptonly when the permissions allow reading files1: the client’s file helpers may read. Without it they refuse, naming the permission, rather than leaving it to the sandboxthe generated client
MCPX_LOG_LEVELmcpx exec / mcpx runalwaysthe lowest level the script’s log keepsthe generated client; mcpx run inside the script or shell reads it as logging.level; also the setting logging.level
MCPX_LOG_SOURCEmcpx exec / mcpx runalwayswhich levels record a call site: a level name for that level and above, 0 for nonethe generated client
MCPX_DAEMON_ENDPOINTthe opencode pluginplugin.env=standard or above, once the plugin has chosen a daemonthe daemon this editor session settled on, so a shell command that runs mcpx talks to the same one instead of looking for its ownmcpx run inside the script or shell, as daemon.endpoint; the plugin, as the daemon to use; also the setting daemon.endpoint
MCPX_TRACE_IDSthe opencode pluginplugin.env=standard or above, when there is anything to sayJSON pairs such as [[“session_id”,“abc”],[“worktree”,“/p”]], extended at full with the parent session and the whole ancestrymcpx run inside the script or shell, which puts every pair on every log record
MCPX_CALL_IDthe opencode pluginplugin.env=standard or abovethe editor’s id for this one tool callthe command
MCPX_OPENCODE_CWDthe opencode pluginplugin.env=standard or abovethe directory the command runs in, which is not always the project rootthe command
MCPX_OPENCODE_DIRECTORYthe opencode pluginplugin.env=standard or abovethe project directory the editor openedthe command
MCPX_OPENCODE_WORKTREEthe opencode pluginplugin.env=standard or abovethe project’s worktreethe command
MCPX_WORKTREE_NAMEthe opencode pluginplugin.env=standard or above, when there is a worktreethe worktree’s last path elementthe command
MCPX_PROJECT_IDthe opencode pluginplugin.env=standard or abovethe editor’s project idthe command
MCPX_HARNESSthe opencode pluginplugin.env=standard or aboveopencodethe command
MCPX_HARNESS_VERSIONthe opencode pluginplugin.env=standard or abovethe editor’s versionthe command
MCPX_HARNESS_PIDthe opencode pluginplugin.env=standard or abovethe editor’s pidthe command
MCPX_HARNESS_STARTEDthe opencode pluginplugin.env=standard or abovewhen the plugin startedthe command
MCPX_SHELL_SEQthe opencode pluginplugin.env=standard or abovea counter of shell commands in this editor processthe command
MCPX_SESSION_TITLEthe opencode pluginplugin.env=fullthe session’s titlethe command
MCPX_SESSION_DIRECTORYthe opencode pluginplugin.env=fullthe session’s directorythe command
MCPX_SESSION_VERSIONthe opencode pluginplugin.env=fullthe editor version that created the sessionthe command
MCPX_SESSION_DEPTHthe opencode pluginplugin.env=fullhow many parents the session hasthe command
MCPX_SESSION_CREATEDthe opencode pluginplugin.env=full, when knownwhen the session was created, ISO 8601the command
MCPX_SESSION_AGE_MSthe opencode pluginplugin.env=full, when knownthe session’s age when the command started, in millisecondsthe command