Skip to content

Configuration Precedence

How ARC-1 resolves config values from CLI args, environment variables, .env files, and defaults — and what changes when you run via npx, a local clone, Docker, or BTP Cloud Foundry.

This page is short on purpose. For the full list of what each env var does, see configuration-reference.md.


The universal rule

For every config field, ARC-1 looks at four sources in this order and uses the first one that's set:

CLI flag   >   process.env   >   .env file (in CWD)   >   built-in default
  • CLI flag--allow-writes, --port, etc. Highest precedence. Always wins.
  • process.env — anything already in the running process's environment when ARC-1 starts. This includes shell exports, docker run -e, Cloud Foundry env, and the env block of an mcp.json (because the MCP client launches ARC-1 as a subprocess with that block as its environment).
  • .env file — loaded by dotenv from process.cwd() at startup (src/index.ts:16). Dotenv never overrides values that are already set in process.env — it only fills in missing keys.
  • Built-in default — defined in src/server/config.ts.

That last point is the one most people miss: if SAP_URL is set in your shell and also in .env, the shell value wins. Putting something in .env doesn't "override" anything — it's a fallback.


What changes per deployment mode

The rule above is universal. What differs across modes is where process.env comes from and whether .env even exists in the CWD.

Mode process.env source .env in CWD? Practical winner
npx arc-1 from a shell shell exports usually no (CWD is wherever you ran npx, not the package cache) shell exports > defaults
npx arc-1 launched by an MCP client (stdio) the client subprocess env (set from the env block in mcp.json / claude_desktop_config.json) no mcp.json env > defaults
npm run dev / npm start from a local clone shell exports yes, the repo's .env shell exports > repo .env > defaults
node dist/index.js launched by an MCP client (stdio) client subprocess env from mcp.json only if you set the client's cwd to a directory that has one mcp.json env > .env in cwd (if set) > defaults
Remote HTTP — client connects via "url" the server's env at startup (set when you launched the server) the server's CWD server-side env > server's .env > defaults. Client-side mcp.json env does nothing.
docker run -e KEY=VAL ... the -e flags + --env-file not in the image; only present if you bind-mount one -e / --env-file > defaults
BTP Cloud Foundry (cf push / cf deploy) manifest.yml / mta.yaml properties: + cf set-env + bound services via VCAP_SERVICES not in the droplet; not deployed manifest/cf-set-env/VCAP > defaults

The one that surprises people: HTTP mode

When an MCP client connects to a remote ARC-1 instance over HTTP — i.e. the client config is

{ "servers": { "sap": { "url": "https://arc1.example.com/mcp" } } }

the env block in mcp.json is not sent to the server. The server is already running with whatever environment was set when it was started (docker run -e ..., cf set-env ..., or your shell). Only the URL and any auth headers (API key / OAuth token) travel from the client.

If you need to change a config value on a remote ARC-1, change it where the server runs and restart, not in mcp.json.


How to debug "which value am I actually using?"

ARC-1 logs an effective-config summary on startup. The most useful lines are:

INFO: auth: MCP=[…] SAP=[…] (shared|per-user) [disable-saml=on?]
INFO: safety: writes=… data=… freeSQL=… transports=… git=… packages=…

Each value is recorded with its source (flag / env / .env / default) internally; the arc1 config show CLI command (when run with the same args / env as your server) prints the resolved value plus the source for every field. Use it whenever the runtime behaviour disagrees with what you thought .env said.

For BTP CF deploys, cf env <app> shows you the final environment as the container sees it (manifest values, cf set-env values, and VCAP_* injected by bound services).


Common pitfalls

  • .env not being read. Dotenv loads from process.cwd(). If you cd /tmp && arc1 …, the .env in your project root is ignored. Either cd into the project or use absolute env vars.
  • Shell exports shadowing .env. export SAP_URL=… in your ~/.zshrc will silently win over a .env file. Unset the shell variable or change .env (or just use the CLI flag for one-off overrides).
  • Quoting glob patterns. SAP_ALLOWED_PACKAGES=* in a shell expands to the contents of the current directory. Use single quotes: SAP_ALLOWED_PACKAGES='*' or -e SAP_ALLOWED_PACKAGES='Z*,$TMP'. Inside .env files no extra quoting is needed.
  • Changing mcp.json on a remote server. As noted above, the env block only applies when the client is spawning the server. For url-based remote connections, change config on the server side.
  • Container .env files. docker run doesn't read .env from your host. Use --env-file path/to/.env or -e flags.
  • ARC1_LOG_HTTP_DEBUG=1 doesn't work. Most boolean env vars accept either "true" or "1", but this one only accepts "true" (a known inconsistency — see the note in configuration-reference.md → Logging and observability).

Restriction lists on BTP: .mtaext is durable, cf set-env is not

SAP_BLOCKED_DATA_SOURCES deserves its own note because getting this wrong silently turns a security control off.

ARC-1 treats a customer .mtaext extension descriptor as the durable desired state for a landscape. Every cf deploy reconciles the application environment toward the descriptor chain. A direct cf set-env SAP_BLOCKED_DATA_SOURCES ... takes effect immediately but is temporary: the next MTA deployment reconciles the property back to whatever the descriptors declare.

You want Do this
Enable the blocklist durably on BTP Set the value in the landscape .mtaext
A temporary incident-response brake cf set-env, then promote the value into the .mtaext
Disable it Write the explicit empty value SAP_BLOCKED_DATA_SOURCES: ""

The base Dockerfile, mta.yaml, manifest.yml and manifest-btp-abap.yml deliberately ship SAP_BLOCKED_DATA_SOURCES="". That is what makes the one-field rollback reliable: an MTA extension can add or override a property but never remove one, so deleting a line does not unset an already-deployed value. If the base omitted the key, a previously deployed non-empty value could survive a deployment intended to clear it.

Verify the effective value after every deploy with arc1 config show or the startup policy log — the log reports whether the policy is enabled, how many entries it has, and a fingerprint you can compare across environments.

See also