Released docs. You are viewing the documentation published with v0.39.0. Development docs are available at Latest.
You have two runtime configurations that pass check, one for the breg-mcp gateway and one for
the breg-review page, and you want both serving behind your own TLS proxy. This page covers what
the proxy owns, how to probe and watch both processes, where their audit journals live, how to
rotate their keys, and how to upgrade them.
Writing the configurations, preparing the registry, and preparing the authorization server are in Configure the citizen chat assistant. To see both services work end to end first, run the local journey.
Put a proxy in front of both services
Section titled “Put a proxy in front of both services”Neither service terminates TLS. With tlsTermination: operator-controlled-upstream, your proxy
owns the public listener and forwards plain HTTP to a loopback or private address. The proxy has
these responsibilities:
- Terminate TLS for both public origins.
- Keep the gateway’s
Host. On/mcp, the gateway refuses with 403 any request whoseHostdoes not match the authority ofresourceServer.resource. Forward the originalHost, or rewrite it to that authority. - Never add an
Originheader. The gateway refuses any/mcprequest that carries one, since a chat host is not a browser page. The refusal happens before the token is verified. - Pass
Authorizationthrough unchanged to the gateway. - Rate limit at the edge. The gateway charges its per-citizen and per-client limits only after
a token verifies, so requests with missing or invalid tokens cost nothing in process. The review
page reads no peer address and no forwarded header, so
limits.globalSignInis one ceiling shared by everyone on/signinand/signin/callback. Limit both services by client address at the proxy, and give the sign-in routes their own tighter limit. - Serve the review page at
publicOrigin. The page builds its sign-in redirect URI frompublicOrigin, so the origin browsers see must be exactly that value.
The gateway checks Host and Origin on /mcp only. /health, /ready, and the
protected-resource metadata routes answer any host.
Probe health and readiness
Section titled “Probe health and readiness”Both services answer GET /health and GET /ready with a JSON body and Cache-Control: no-store. Probe them over HTTP from your platform.
| Route | breg-mcp | breg-review |
|---|---|---|
GET /health | 200 {"status":"alive"} | 200 {"status":"alive"} |
GET /ready, journal writable | 200 {"status":"ready"} | 200 {"status":"ready"} |
GET /ready, journal not writable | 503 {"status":"not-ready"} | 503 {"status":"not-ready"} |
Readiness reflects the audit journal alone. Neither route calls the registry or the authorization
server, so a ready service can still answer a tool call with registry-unavailable or a sign-in
with an error page. Watch the warnings in the next section for those.
Read the logs
Section titled “Read the logs”Each service writes one JSON object per line on standard output. The level is set by
BREG_MCP_LOG or BREG_REVIEW_LOG, which accept exactly error, warn, or info and default to
info. Any other value stops the process before it starts, with a message on standard error and
exit status 2. See environment variables.
Startup failures take different channels. breg-mcp writes an ERROR JSON line on standard output
and exits with status 1. breg-review writes a plain line starting breg-review: on standard error
and exits with status 1. Collect both streams.
These records are the ones to watch:
| Service | Level | Message |
|---|---|---|
breg-mcp | info | tool call, with request_id, tool, and outcome |
breg-mcp | warn | the access-token key set is unavailable |
breg-mcp | error | tool audit append failed |
breg-review | warn | a rate limit refused a request |
breg-review | warn | the registry could not answer a review |
breg-review | warn | the registry declined a review request |
breg-review | error | an action request could not be audited |
breg-review | error | an action response could not be audited |
Neither service logs a token, a secret, a cookie, or a field value. The request_id of a gateway
log record matches the operation correlation in its audit entries.
Keep the audit journals
Section titled “Keep the audit journals”Each service writes its own audit stream, separate from the registry’s audit. The registry still audits the reads and writes it performs for the gateway’s delegated token.
| Service | Operations | Pseudonym classes |
|---|---|---|
breg-mcp | Tool calls | breg-mcp-principal-v1, breg-mcp-client-v1 |
breg-review | Admitted sign-in callbacks, reads, submissions, and sign-outs | breg-review-principal-v1, breg-review-client-v1 |
Each line carries schema, eventId, time, phase, correlation, and record.
A request entry precedes protected I/O; a response entry precedes release of the result.
Both carry the same fresh operation correlation. A change-request identifier identifies the
record under review; it does not identify the operation, since multiple reviews can overlap.
A gateway response entry records the tool call’s outcome. It is ok when the tool answered,
refused when the call failed with no registry effect, and unfinished when a create or patch
was sent and its effect is unknown: a transport failure, a server error, or a response the gateway
cannot use. A failed call also records its tool error code
as reason. A call answered unfinished may have changed the registry. A cancelled call is also
recorded unfinished, whether or not it had sent a create or patch.
Citizen and client identifiers become keyed HMAC-SHA-256 pseudonyms derived from the issuer and subject or client identifier. No field value, prompt, tool argument, token, cookie, or network address belongs in the stream. Before sign-in verifies a subject, its records omit the citizen pseudonym. Different service classes give the same citizen different pseudonyms.
A refused request append prevents protected I/O. A refused response append withholds the result,
but does not undo a registry effect or restore a revoked session. If an operation is cancelled,
the request guard attempts a minimized unfinished response while the process and writer can
still complete it. A crash or failed destination can leave a request without a response;
responses can also be duplicated. Reconcile by correlation and registry state, not by assuming
one terminal line proves exactly one effect.
For durable local acceptance, use audit.destination: file and give each process its own absolute
audit.path. The runtime creates a missing directory, refuses group- or world-writable audit
directories, and keeps files owner-only. The active file has .lock and .seq companions. Keep
those companions in place while the stream is in use.
Files rotate at audit.rotateBytes, 100 MiB by default. Sealed files expire after
audit.retainDays, 90 days by default. Ship sealed files to append-only storage before expiry;
the local files have no hash chain and cannot prove completeness. audit.destination: stdout
is best effort: flushing a line does not prove durable storage. A failed writer stays unavailable
until restart, so investigate the operator diagnostic and destination before restarting.
Rotate keys and secrets
Section titled “Rotate keys and secrets”Both services read every secret once, at startup. Replacing a secret file takes effect only on the next restart.
Client keys. The gateway’s exchange client and the review page’s sign-in client each authenticate with a private JWK. To rotate one, register the new public key with the authorization server beside the old one, replace the secret file, restart the service, and then remove the old public key.
Issuer signing keys. With jwksSource.kind: discovery or uri, the gateway refetches the
discovered or configured key set when a token names
a key it does not hold, so the authorization server can publish a new key and sign with it without
a gateway restart. With jwksSource.kind: static, add the new public key to the key set secret and
restart before the authorization server signs with it. The review page always fetches the key set
named by the issuer’s discovery document.
Audit keys. Keep the key stable across restarts: it preserves pseudonym correlation and the
gateway’s deterministic retry identity. Changing it changes both. Arrange a controlled restart
with no outstanding gateway retries, archive the old stream, and select a fresh audit.path.
Retain the old key securely for as long as your policy requires recomputing old pseudonyms.
The new writer does not verify a hash chain or detect a changed pseudonym key.
Upgrade
Section titled “Upgrade”When upgrading from the draft chained format, archive its files and configure a fresh audit path.
Set each listener.bind explicitly.
Run check from the new binary against the revised configuration, then replace the
binary and restart:
breg-mcp --runtime-config /etc/breg-mcp/runtime.yaml checkbreg-review --runtime-config /etc/breg-review/runtime.yaml checkBoth services stop accepting connections on SIGTERM or SIGINT and finish the requests already
in flight. A restart loses different state in each:
breg-mcpkeeps no session between requests. Drafts live in the registry, so a chat host carries on after a restart.breg-reviewkeeps sessions and pending sign-ins in memory. A restart signs every citizen out and abandons sign-ins in progress; the citizen opens the review link again and signs in.
For the same reason, run one review page instance, or route each browser to the same instance with sticky sessions. The in-process limits count per instance, so each replica you add raises the effective limit.
Install the services or run their images
Section titled “Install the services or run their images”The first release eligible to publish the paired services is v0.38.0. For a published release at
or after that boundary, choose <tag> from the
latest release, download the
breg-mcp-<tag>-linux-amd64 and breg-review-<tag>-linux-amd64 assets, and authenticate both
through the shared SHA256SUMS procedure at
https://github.com/registrystack/registry-stack/blob/<tag>/release/VERIFY.md. Install the exact
pair from one tag:
tag="${TAG:?set TAG to a published tag that includes breg-mcp and breg-review}"mkdir -p ~/.local/binfor binary in breg-mcp breg-review; do install -m 0755 "${binary}-${tag}-linux-amd64" "$HOME/.local/bin/${binary}"doneexport PATH="$HOME/.local/bin:$PATH"breg-mcp --versionbreg-review --versionThe install replaces either file already at that destination. Preserve the old pair first when you need a rollback path. Build both services from a checkout when no published release contains them or when you need another platform.
The same release publishes ghcr.io/registrystack/breg-mcp:<tag> and
ghcr.io/registrystack/breg-review:<tag>. Each image carries one binary on Distroless
cc-debian13 as the nonroot user. Authenticate the image digests through the release manifest
and deploy the digest for each service rather than the tag alone.
Mount the runtime configuration and the secrets read-only, and give the journal a writable
directory the image user owns. Inside a container, bind the listener on 0.0.0.0, which requires
networkExposure: container-private:
listener: bind: 0.0.0.0:8110 tlsTermination: operator-controlled-upstream networkExposure: container-privateA gateway container then looks like this, with the review page following the same pattern on port 8115:
BREG_MCP_IMAGE='ghcr.io/registrystack/breg-mcp@sha256:<verified-digest>'docker run --rm \ --mount type=bind,source=/etc/breg-mcp/runtime.yaml,target=/etc/breg-mcp/runtime.yaml,readonly \ --mount type=bind,source=/run/secrets/breg-mcp,target=/run/secrets/breg-mcp,readonly \ --mount type=volume,source=breg-mcp-audit,target=/var/lib/breg-mcp \ --publish 127.0.0.1:8110:8110 \ "$BREG_MCP_IMAGE"Set audit.path to a file in the mounted journal directory, such as
/var/lib/breg-mcp/audit.jsonl. The image user owns that directory, and the audit journal
refuses a directory it does not own. Run check in the same image before you serve:
docker run --rm \ --mount type=bind,source=/etc/breg-mcp/runtime.yaml,target=/etc/breg-mcp/runtime.yaml,readonly \ --mount type=bind,source=/run/secrets/breg-mcp,target=/run/secrets/breg-mcp,readonly \ "$BREG_MCP_IMAGE" \ --runtime-config /etc/breg-mcp/runtime.yaml checkThe secret files must be readable by the image user. The image carries no shell and no HTTP
client, so probe /health and /ready from the platform.