Skip to content
docs
Arxo ↗

Deployment overview: SDK vs CLI vs MCP vs law serve

For LLMs11 sections

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.

SurfaceEntry pointTransportAuth in the toolCorpus scope
CLIlaw ask, law case …local processnone (OS user boundary)case package in cwd
stdio MCPlaw-mcp-server (default)JSON-RPC lines on stdin/stdoutnone (OS process boundary)--profile slice
HTTP MCPlaw-mcp-server --httpPOST /mcpAuthorization: Bearer when --token set--profile slice
law servelaw serve --world … --journal …POST /v1/ask + service routesAuthorization: Bearer when --token setone pinned world
Remote SDK@arxo/law-client/mcpStreamable HTTP, one POST /mcp per messagecaller-supplied headerwhatever the server serves
Embedded SDK@arxo/law (evaluation via @arxo/law-engine)in-process calls, no servernone (caller process boundary)bundled engine + caller-supplied data

Facts behind the table, each read from its implementation body:

  • law-mcp-server serves 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=1 as deliberate consent; law-serve-core/src/service.rs Config::validate, law-mcp/src/http.rs Config::check).
  • Neither HTTP server terminates TLS: there is no --tls/--cert flag 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.

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.

Output
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):

  1. 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.
  2. HTTP surfaces (--http MCP, law serve) bind loopback by default (127.0.0.1; MCP port default 8722, serve port default 8480) 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.
  3. law serve answers only from the pinned world: a request whose pin.{world,resolutionHash} differs from the loaded law.lock is refused with SERVE_PIN_MISMATCH (HTTP 409, law-serve-core/src/codes.rs).

For a private service that other machines call, the supported main route is law serve with a journal, on loopback, behind an operator ingress:

Terminal
law serve --world /srv/cases/kompaniya --journal /srv/cases/kompaniya-journal \
--host 127.0.0.1 --port 8480 --token-file /srv/secrets/serve-token

Why 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.

  • The law binary installed by one route in Install; confirm with law version.
  • The law-mcp-server binary for MCP articles (crate law-mcp, [[bin]] name = "law-mcp-server" in its Cargo.toml).
  • For law serve: a case-package directory containing law.toml and law.lock (the world; World::load refuses without both).
  • For MCP: a checkout root holding the profiles directory and the CLIR tree (found by root discovery), or pass --root explicitly. Each profile is one <name>.json file in the profiles directory.
  • Node.js only if you use the SDK (@arxo/law-client, ESM import).
  • CLI to law serve: same world directory. What law ask [<case>] … answers locally, POST /v1/ask answers remotely; the envelope changes ({pin, case, query|evaluate, …}), the bytes of the decision do not.
  • stdio MCP to HTTP MCP: add --http (plus --token before 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 one POST /v1/ask envelope, the corpus slice becomes one pinned world, and answers gain journal records plus Idempotency-Key replay.
  • 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.
  • unknown flag … (exit 2) from either server: the flag name is wrong; both usage strings are printed with --help (-h for MCP).
  • SERVE_WORLD_INVALID (HTTP 500 at startup, process exits): world directory lacks law.toml/law.lock, or a profile.json path sits outside deps/profiles/<id>/<version>/.
  • address … without a token is refused (startup refusal): a non-loopback --host needs --token/--token-file, or deliberate --public.
  • --token and --token-file cannot be combined; --journal and --no-journal cannot be combined (startup refusal, exit 2).
  • “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=1 without a token are development/deliberate-consent modes (reference-settings, not defaults): unrecorded decisions never replay, and an unauthenticated evaluator answers anyone who reaches it.

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.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.