Deployment overview: SDK vs CLI vs MCP vs law serve
Choose the right deployment surface for private use: local CLI, MCP over
stdio, MCP over HTTP, law serve HTTP, or the typed SDK — and understand
which trust boundary each one crosses.
Components: law CLI (usages quoted from the
CLI reference, tool 0.1.1), law-mcp-server binary
(crate law-mcp), @arxo/law-client 0.2.0 (the npm client package).
Scenario: a private, single-organization deployment; no public endpoint.
All host names, tokens, and hashes below are synthetic examples.
The five surfaces
Section titled “The five surfaces”| Surface | Entry point | Transport | Auth in the tool | Corpus scope |
|---|---|---|---|---|
| CLI | law ask, law case … | local process | none (OS user boundary) | case package in cwd |
| stdio MCP | law-mcp-server (default) | JSON-RPC lines on stdin/stdout | none (OS process boundary) | --profile slice |
| HTTP MCP | law-mcp-server --http | POST /mcp | Authorization: Bearer when --token set | --profile slice |
law serve | law serve --world … --journal … | POST /v1/ask + service routes | Authorization: Bearer when --token set | one pinned world |
| Remote SDK | @arxo/law-client/mcp | Streamable HTTP, one POST /mcp per message | caller-supplied header | whatever the server serves |
| Embedded SDK | @arxo/law (evaluation via @arxo/law-engine) | in-process calls, no server | none (caller process boundary) | bundled engine + caller-supplied data |
Facts behind the table, each read from its implementation body:
law-mcp-serverserves stdio by default and HTTP only with--http(law-mcp/src/main.rs,usage()).- Both HTTP servers refuse a non-loopback bind without a token unless the
operator passes the explicit opt-out (
--token/LAW_MCP_TOKEN, or--public/LAW_MCP_PUBLIC=1as deliberate consent;law-serve-core/src/service.rsConfig::validate,law-mcp/src/http.rsConfig::check). - Neither HTTP server terminates TLS: there is no
--tls/--certflag in either usage string. Encryption to the outside world is an operator reverse proxy (operator-policy-example, see article 02). - The remote SDK computes nothing: it folds the envelope, parses the envelope, and validates the response shape (transport and shape validation only). The embedded SDK is the opposite choice — it evaluates inside the caller’s process with no server at all, so case data never crosses a socket but the caller inherits engine versioning, resource use, and supply-chain trust directly. Pick the row that matches where evaluation must happen.
Component and trust-boundary diagram
Section titled “Component and trust-boundary diagram”Created-example layout; boxes are real components, arrows are real
protocols, the ||| column separates machines. stdio stays on the
laptop (spawned child process); only HTTP crosses to the server
host; the outbound legs leave whichever machine runs the server.
LAPTOP (local) ||| PRIVATE SERVER HOST ============== ||| ====================
MCP client spawns the server as a child ||| process; stdio never crosses the network: ||| ||| +----------+ stdio pipe, no auth +--------+||| +------------------+ | agent / | --------------------> | law- |||| | law-mcp-server | | editor | (OS process is the | mcp- |||| | --http, loopback | +----------+ trust boundary) | server |||| | --profile <slice>| +--------+||| +--------+---------+ ||| ^ HTTP clients (anywhere) reach the host ||| | POST /mcp over the network with a Bearer <token> ||| | + Bearer ||| | +----------+ POST /mcp or /v1/ask +--------+||| +--------+---------+ | remote | Bearer <token> through an ||| | law serve | | agent / | operator TLS edge ||| | --world (pinned) | | app (SDK)| -----------------------------> ||| | --journal (fsync | +----------+ ||| | before serving) | ||| +------------------+ Outbound legs (if set) leave whichever ||| machine runs the server: answers / neural ||| / OTLP over HTTPS. |||Boundary rules (implementation facts, not advice):
- stdio surfaces (CLI, stdio MCP) have no in-tool authentication. Anything that can spawn the process or write to its stdin can ask. “Isolated” here means only: reachable solely through the OS process boundary.
- HTTP surfaces (
--httpMCP,law serve) bind loopback by default (127.0.0.1; MCP port default8722, serve port default8480) and require a token for anything wider. “Secured” in these articles means only: loopback bind and/or Bearer token checked by the server, plus an operator TLS edge — never a claim about the strength of the law reasoning itself. law serveanswers only from the pinned world: a request whosepin.{world,resolutionHash}differs from the loadedlaw.lockis refused withSERVE_PIN_MISMATCH(HTTP 409,law-serve-core/src/codes.rs).
Supported main route
Section titled “Supported main route”For a private service that other machines call, the supported main route is
law serve with a journal, on loopback, behind an operator ingress:
law serve --world /srv/cases/kompaniya --journal /srv/cases/kompaniya-journal \ --host 127.0.0.1 --port 8480 --token-file /srv/secrets/serve-tokenWhy this one: it is a public CLI command (see the
CLI reference), every
decision is recorded before it is served (replayable with
law replay-record --world <dir> --journal <dir> --decision <id>),
and the pin check makes stale-client / rotated-world skew loud
instead of silent. Full procedure is
Deploy a private HTTP service;
its acceptance branch is the serve branch of
Verification and support matrix.
The section runs two explicit branches from here:
- Serve branch (this main route): launch (02) → auth and TLS (06, 02-Step-3) → readiness and metrics (02, 12-serve-rows) → journal (02, 09-serve-rows) → upgrade, backup, rollback (15, 16, 17) → acceptance (20, checks S1–S10).
- MCP branch: launch (03) → profile and token (07, 06) → edge (05) → call logs and artifacts (09, 07-matrix) → timeout and load (11, 18) → acceptance (20, checks M1–M8).
Each shared article opens with an “Applies to” table naming which branch its recipe covers and where the other branch lives.
Use stdio MCP instead when the only consumer is an agent or editor on the same machine (article 03). Use HTTP MCP instead when MCP-shaped clients must reach the corpus over the network but no pinned decision journal is needed. Use the SDK when calling from JavaScript and you want envelope folding plus response-shape validation without subprocesses.
Requirements
Section titled “Requirements”- The
lawbinary installed by one route in Install; confirm withlaw version. - The
law-mcp-serverbinary for MCP articles (cratelaw-mcp,[[bin]] name = "law-mcp-server"in itsCargo.toml). - For
law serve: a case-package directory containinglaw.tomlandlaw.lock(the world;World::loadrefuses without both). - For MCP: a checkout root holding the profiles directory and the
CLIR tree (found by root discovery), or pass
--rootexplicitly. Each profile is one<name>.jsonfile in the profiles directory. - Node.js only if you use the SDK (
@arxo/law-client, ESM import).
Transitions
Section titled “Transitions”- CLI to
law serve: same world directory. Whatlaw ask [<case>] …answers locally,POST /v1/askanswers remotely; the envelope changes ({pin, case, query|evaluate, …}), the bytes of the decision do not. - stdio MCP to HTTP MCP: add
--http(plus--tokenbefore any non-loopback--host). Clients switch from spawned-command config to an HTTP endpoint; the tool table is the same for the same--profile. - HTTP MCP to
law serve: a real change of surface, not a flag flip. MCP tools (law_ask,law_search, …) become onePOST /v1/askenvelope, the corpus slice becomes one pinned world, and answers gain journal records plusIdempotency-Keyreplay. - Any surface to a new world/slice: for serve, repoint
--world(or rotate the symlink target under--watch-world); for MCP, restart with another--profile. Old pins keep failing loudly against the new world.
Failures and diagnostics
Section titled “Failures and diagnostics”unknown flag …(exit 2) from either server: the flag name is wrong; both usage strings are printed with--help(-hfor MCP).SERVE_WORLD_INVALID(HTTP 500 at startup, process exits): world directory lackslaw.toml/law.lock, or aprofile.jsonpath sits outsidedeps/profiles/<id>/<version>/.address … without a token is refused(startup refusal): a non-loopback--hostneeds--token/--token-file, or deliberate--public.--token and --token-file cannot be combined;--journal and --no-journal cannot be combined(startup refusal, exit 2).
Support boundaries
Section titled “Support boundaries”- “Implemented”, “described”, “verified”, and “supported” follow the binding definitions in Verification and support matrix: code existence alone never earns a stronger word. Anything labelled created-example (nginx/TLS configs, systemd units, client JSON, user accounts, token values) is an operator sketch, not a tested artifact.
- No surface here performs TLS, rate limiting, or multi-tenant isolation by itself; those are operator layers outside the cited code.
--no-journal(serve) and--public/LAW_MCP_PUBLIC=1without a token are development/deliberate-consent modes (reference-settings, not defaults): unrecorded decisions never replay, and an unauthenticated evaluator answers anyone who reaches it.
Result check
Section titled “Result check”You can name, for your consumer, the surface, its transport, where its trust boundary lies, and what the main-route command is. If the consumer is another machine and you need replayable decisions, proceed to article 02; if it is a local agent, to article 03.
Next step
Section titled “Next step”- 02 Private HTTP service: end-to-end
law serve. - 03 Private MCP server: stdio and HTTP MCP.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.