MCP server
MCP design principles
Eight principles, each with what it means in practice.
| Principle | In practice |
|---|---|
| Projection, not a brain | Zero own read paths — every request calls the same composition seams the CLI verbs use, and renders with the same renderers. Zero cache beyond the read; state is fresh from the record on every request. reload (SIGHUP) swaps configuration only, never state. The one thing held for the life of the process is the authorization server's own signing key and client registry: a change to either is seen after a restart. |
| Read-only without exception | Eleven read tools. Mutating tools do not exist, not even as stubs. An unknown tool is refused with a message that names the full tool set; an unknown method is refused with a message saying the server serves tools only. |
| One contract, both surfaces | The tool registry is data in the service layer, consumed by every transport from one function — the tool list cannot fork. Payload parity between MCP and CLI is a conformance test on the real binary (deep-equal across the eight record-projection tools). The three aggregates have no CLI verb to be equal to — they are rollups over the same store, served through the same registry, and the parity scenario covers the eight tools that project a CLI verb's payload. |
| The descriptor is a commitment, not a comment | Tool descriptions enumerate response content exhaustively, and a test fails when descriptor and behaviour drift. ⚠ The descriptions of fleet_show, wave_show and ledger_show still name presentation contract v3; the contract_version field in every response (4 in this release) is the authoritative one. |
| Refusal, not silence | A response over the byte cap is an error naming the exact narrowing to apply — never a silent truncation for a downstream layer to mangle without a marker. A limit above the maximum is rejected, never clamped. A bind address with an implicit host is refused at every configuration tier alike. |
| Denial-as-data | Every access denial is ledgered as a zero-cost refusal row with tool, cause, correlator, tenant and actor claim. The wire carries an opaque reference; the cause lives only in the record. |
| Fail-closed scope | An absent scope denies every class-gated tool. The one intentional unrestricted grant on this surface (the local stdio operator) goes through a single named constructor, never a silent default. |
| Deterministic first | No model call exists anywhere on the serving path. |