Configuration reference
Give every setting of the two network executables in one place: each setting’s type, default, source (flag or environment), precedence, validation, whether a restart is needed, and whether it is secret — with links to the scenario articles that use it.
Components: law-mcp-server (binary name in
law-mcp/Cargo.toml; its help text prints law-mcp)
and law serve (law-bin-core/src/serve.rs,
law-serve-core/). Scenario: any deployment that
starts these processes. All values below are synthetic examples. Words
like default, implementation-limit, reference-setting, and
operator-policy-example are used with distinct meanings (see the last
table); implemented / described / verified / supported follow the
section vocabulary.
Prerequisites
Section titled “Prerequisites”- A checkout or release tree containing the binaries.
- For
law-mcp-server: a corpus profile name (one JSON file in the checkout’s profiles directory, orall). - For
law serve: a world directory and a journal directory (see Deploy a private HTTP service).
law-mcp-server settings
Section titled “law-mcp-server settings”Source rule (verified in src/main.rs, Args::parse): every setting
first reads its environment variable, then a command-line flag
overwrites it. Precedence is therefore flag > environment >
built-in default for settings that have both; settings with only an
environment source use environment > built-in default. All settings
take effect only after a process restart: every change needs a
restart. The four external-leg variables are read from the
process environment on every call, but that is not live reload —
editing the parent shell or the systemd EnvironmentFile does not
change the environment of the already-running process, so a new
value still needs a restart (or relaunch) to take effect. There is
no configuration file and no reload signal.
| Setting | Type | Default | Source | Validation | Restart | Secret |
|---|---|---|---|---|---|---|
| Root tree | path | auto-detected (find_root from cwd) | LAW_MCP_ROOT, --root | must hold the profiles directory for named profiles | yes | no |
| Profile | string | all (empty env counts as unset) | LAW_MCP_PROFILE, --profile | all, or an existing profile file whose facets name each known facet exactly once | yes | no |
| HTTP mode | bool | off (stdio JSON-RPC on stdin/stdout) | --http only | — | yes | no |
| Host | string | 127.0.0.1 | LAW_MCP_HOST, --host | non-loopback without a token is refused unless public (exact error names LAW_MCP_TOKEN and LAW_MCP_PUBLIC=1) | yes | no |
| Port | u16 | 8722 | LAW_MCP_PORT, --port | must parse as u16 | yes | no |
| Token | string | none | LAW_MCP_TOKEN, --token | exact Authorization: Bearer <token> match per request; flag wins over env; empty env counts as unset but empty --token "" is a real empty token (quirk: it also permits a public bind while demanding Authorization: Bearer with a trailing space) | yes | yes |
| Public | bool | off | LAW_MCP_PUBLIC=1, --public | explicit consent to serve without a token | yes | no |
| Allowed origins | comma list / repeatable | empty (any Origin header is refused with 403) | LAW_MCP_ALLOWED_ORIGINS, --allowed-origin | exact string match against the Origin header | yes | no |
| Call timeout | seconds, fractions allowed | none (no limit) | LAW_MCP_CALL_TIMEOUT only | non-negative number; empty or 0 means no limit | yes | no |
| Journal on/off | bool | on | LAW_MCP_LOG=0 disables | — | yes | no |
| Journal body cap | bytes per side | 65536 (64 KiB) | LAW_MCP_LOG_MAX_BODY only | must parse as usize; 0 means no cap | yes | no |
| Journal socket | path | /run/systemd/journal/socket | LAW_MCP_LOG_SOCKET only | if the socket is missing, records fall back to one JSON line on stderr | yes | no |
| OTLP receiver | HTTP(S) URL | none (disabled) | LAW_MCP_OTLP_URL only | non-empty string; spans POST as JSON to exactly this URL | yes | no |
| Answers service URL | HTTPS URL | none (the 4 publication tools answer “unavailable”) | LAW_ANSWERS_URL only; stdio + HTTP; read per call | non-empty; http loopback-only; credentials-in-URL rejected | yes | no |
| Answers service token | string | none (same unavailability) | LAW_ANSWERS_TOKEN only; stdio + HTTP; read per call | required non-empty together with the URL; sent as Authorization: Bearer | yes | yes |
| Neural service URL | HTTPS URL | none (lexical-only answers) | LAW_NEURAL_URL only; stdio + HTTP; read per call | same URL rules as above | yes | no |
| Neural service token | string | "" (appendix inactive) | LAW_NEURAL_TOKEN only; stdio + HTTP; read per call | empty refused by the client (SERVICE_CONFIG → inactive appendix); sent as Authorization: Bearer | yes | yes |
| Artifact dir | path | $HOME (required at call time) | LAW_MCP_ARTIFACT_DIR only | missing dir and missing $HOME fail the call with ANSWER_RESOURCE_UNAVAILABLE | yes | no |
Two unit templates (reference-settings, not code defaults) — never mix them:
- Private (the model for every closed deployment in this
section):
kit/secure-deploy/law-mcp-private@.servicesetsLAW_MCP_PORT=%i,LAW_MCP_HOST=127.0.0.1,LAW_MCP_CALL_TIMEOUT=110,LAW_MCP_LOG=1,MemoryMax=6G,User=arxo-mcp,NoNewPrivileges=true,RestartSec=3, and reads the token fromEnvironmentFile=/etc/arxo/mcp-%i.env. It contains noPUBLICline: without a usable token the process refuses to serve instead of opening up. - Public (deliberate open bind only): the shipped
arxo-ops/deploy/mcp/law-mcp@.serviceaddsLAW_MCP_PUBLIC=1. Copying that line into a private instance silently converts a missing token from a startup refusal into an open evaluator — keep it out of every closed recipe.
Flags that are commands, not settings: --healthcheck (probes
127.0.0.1:<port>/healthz with a 4 s timeout, exit 0/2; honors
--port), --export-neural-input PATH (writes the search index
document and exits), -h/--help.
Implementation-limits (constants, not settings): request body cap
MAX_BODY = 1 MiB (src/http.rs); no request batching; no SSE
(GET /mcp → 405); no sessions (DELETE → 405).
law serve settings
Section titled “law serve settings”Source rule (verified in serve.rs parse + service.rs
Config::default): flags only — law serve reads no environment
variable for its own configuration. All flags are parsed once at
startup: every change needs a process restart.
| Setting | Flag | Type | Default | Validation | Restart | Secret |
|---|---|---|---|---|---|---|
| World | --world (required) | dir or pinned profile.json | none | dir, or a file named profile.json under deps/profiles/<id>/<version>/ matching the lock; a .arxo bundle is refused — restore the world dir from the bundle first | yes | no |
| Journal | --journal / --no-journal | dir / bool | none — one of the two is required | --no-journal is development only (unrecorded decisions never replay) | yes | no |
| Watch world | --watch-world | bool | off | watches the --world symlink target, warms the new world; the journal stays shared, new records carry the new pin | yes | no |
| Host | --host | string | 127.0.0.1 | non-loopback without a token is refused unless --public | yes | no |
| Port | --port | u16 | 8480 | must parse | yes | no |
| Token | --token / --token-file (mutually exclusive) | string / path | none | exact Authorization: Bearer <token> match; empty file token refused | yes | yes |
| Public | --public | bool | off | explicit consent to bind without a token on any address; requires the process-worker path (the CLI always sets it to its own executable, serve.rs) | yes | no |
| Workers | --workers | usize | 2 | at least 1 | yes | no |
| Queue | --queue | usize | 64 | at least 1 | yes | no |
| Max body | --max-body | bytes | 1048576 (1 MiB) | must parse | yes | no |
| Max assertions | --max-assertions | usize | 10000 | must parse | yes | no |
| Max response | --max-response | bytes | 16777216 (16 MiB) | must parse | yes | no |
| Call timeout | --call-timeout-ms | ms | 30000 (30 s) | positive number | yes | no |
| Journald socket | --log-socket | path | none (JSON to stderr) | call fields to native journald when set | yes | no |
| OTLP receiver | --otlp-url | HTTP URL | none (disabled) | must start with http:// or https:// | yes | no |
Implementation-limits (no flag): socket I/O timeout 10 s
(io_timeout), journal segment cap 64 MiB (max_segment),
Idempotency-Key 1–255 printable ASCII characters without spaces.
The law launcher
Section titled “The law launcher”The ./law shell script reads one variable: LAW_PYTHON — path to a
Python ≥ 3.12 with jsonschema and fastjsonschema for law dev
subcommands. It is launcher-only: neither Rust executable reads it.
- Pick the scenario article first (Deploy a private HTTP service for
law serve; HTTP, TLS and reverse proxies for the MCP edge): copy its exact command, not the tables above. - Set secrets through the file/env channel the scenario names
(
--token-file,LAW_MCP_TOKENvia a systemdEnvironmentFile), never on a shared command line. - Start the process and read the stderr launch line:
law-mcp-serverprints address, profile, journal channel, call limit, and OTLP receiver; that line is the effective configuration. - Change any setting by editing the unit/flags and restarting the process.
Expected result
Section titled “Expected result”law-mcp-server --http --port 8722on loopback without a token starts and answersGET /healthzwith{"status":"ok",...}(HTTP 200).law serve --world <dir> --journal <dir>starts after all workers warm up;GET /readyzreturns 200.- A non-loopback bind without a token exits before listening with the refusal quoted above (exit code 2).
Result check
Section titled “Result check”law-mcp-server --healthcheck --port 8722; echo "exit=$?"curl -s http://127.0.0.1:8722/healthzcurl -s http://127.0.0.1:8480/readyz -o /dev/null -w "%{http_code}\n"Success: exit 0, a body containing "status":"ok", and 200 for
/readyz. These commands are created-examples (paths and ports are
synthetic); the flags, routes, and fields they use exist in code.
Failures and diagnostics
Section titled “Failures and diagnostics”| Symptom | Cause | Fix |
|---|---|---|
bind on "0.0.0.0" without LAW_MCP_TOKEN is refused | non-loopback HTTP without token/public | bind loopback, or set a token, or set LAW_MCP_PUBLIC=1 deliberately |
address "0.0.0.0" without a token is refused | same for law serve | --token/--token-file, or deliberate --public |
LAW_MCP_PORT: ... / --port: ... | unparsable port | pass a 0–65535 integer |
facets.allow contains duplicates / each known facet must be named exactly once | profile names a facet twice or misses one | fix the profile file (see Tool profiles) |
OTLP URL: expected http:// or https:// | bad law serve --otlp-url | pass a full HTTP(S) URL |
--token and --token-file cannot be combined | both passed | keep one |
direct launch requires worker_exe | library misuse, not CLI | start via the law serve command |
Support boundaries
Section titled “Support boundaries”- Changing any setting without restarting the process is not
supported. The four external-leg variables are re-read per call
from the process environment, but a changed shell or
EnvironmentFilevalue still reaches the process only through a restart (or relaunch); everything else binds at startup. - There is no configuration file format: any
.toml/.yamlsnippet for these executables is an operator-policy-example, not a server-read file. - The verified constants with no setting are the 1 MiB body cap, the 10 s I/O timeout, and the 64 MiB segment: tuning them requires a code change. Anything else absent from these tables is either a constant the author did not verify (treat as unsupported until checked) or a new setting — not a hidden option you can guess.
Vocabulary used in this series
Section titled “Vocabulary used in this series”| Word | Meaning | Example |
|---|---|---|
| Default | value the code uses when the operator sets nothing | port 8722 |
| Implementation-limit | constant behavior with no setting | 1 MiB body cap |
| Reference-setting | value from a shipped deploy file, still overrideable | LAW_MCP_CALL_TIMEOUT=110 in law-mcp-private@.service |
| Operator-policy-example | this documentation’s suggestion, not read by code | run as a dedicated OS user |
| Created-example | synthetic value (path, token, host) safe to copy structurally | LAW_MCP_TOKEN=REDACTED_EXAMPLE_TOKEN |
Next step
Section titled “Next step”- To expose MCP over HTTPS: HTTP, TLS, reverse proxies.
- To choose token vs public: Authentication and access control.
- To narrow the tool surface per profile: Tool profiles and input policy.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.