This document explains why the utility is built the way it is: its commands, the decisions
behind them and the alternatives they rejected. What it is is described by its godoc (doc.go).
rat-tool holds every secondary feature, so that ratd and rat keep a single mode each: a service
and a bridge are configured once and started by a supervisor or a harness, where a utility is run
by a human, command after command.
rat-tool bundle generate --tenant NAME --client NAME [--client NAME…] --output DIR
rat-tool bundle inspect DIR [DIR…]
rat-tool check --server HOST:PORT --bundle DIR
bundle generate
Generates the bundle of a tenant (see package mtls): the tenant and at least one client are
required, validated as tmux names, the client names unique. It runs wherever the admin trusts:
nothing in a bundle is tied to the server nor to a client. The output directory must not exist,
as replacing a bundle silently breaks every client deployed with it: the error says so, and how
to regenerate (remove it, or choose another one). It prints the tenant, the directory of each
side and what it is for, the CA fingerprint and the expiry, and reminds to copy each directory
to its side only.
bundle inspect
Offline, for each directory: the checks ratd and rat run at startup, one line per check until
one fails (mtls.Checks: files of a side, key readable by its owner only, key matching the
certificate, certificate signed by the CA, of its role only, certificates valid now, plain
names), then which side it is (from its certificate), its name (the tenant, or the client, which
is its session), the CA fingerprint and the remaining validity, with a warning under a year.
Given several directories, it tells whether they belong to the same bundle (the same CA): mixing
the files of two bundles is the likeliest mistake, and a handshake only reports it as an opaque
error.
Its exit code is not zero when a directory fails a check, or when the directories belong to
several bundles: checking that directories go together is what inspecting several is for, and
scripts rely on the exit code.
check
Checks a running ratd from a client directory, with the flags of rat, declared once in package
internal/flags: a harness command line is checked by copying it. Step by step, stopping at the
first failure with a message proper to that step:
- Files: the checks of
bundle inspect, and a client directory rather than the server's.
- Network: the address answers (TCP, 10 seconds at most), on the connection the TLS step
then uses: one closed without a handshake would show in the logs of ratd as a refused one.
- TLS: the handshake, and when it fails, why. It reports the tenant, from the server
certificate, and the session, from the client certificate. In TLS 1.3, a client completes its
handshake before the server has checked its certificate: a refusal only shows on the first
read. A plain HTTP request follows the handshake, for that read to come at once rather than
after waiting on a timer (any status will do). A refusal by ratd is explained from its alert
(see package
mtls): a certificate of another bundle of the tenant, of another tenant, of
another role, expired. A server refused by this client is told as package mtls tells it.
- MCP: the client of the Go SDK connects (initialize, or discover from protocol 2026-07-28
on), through
connect.Transport, then shows the server information, the protocol version,
and the instructions, which carry the bracketed paste warning when ratd has one: a human sees
it too.
- Tools: their names and descriptions.
Files: clients/alice
ok files of a side
…
client alice of tenant prod, working in session alice
Network
ok host:7281 answers (TCP, 3 ms)
TLS
ok ratd serves tenant prod, and accepts this client as session alice
MCP
ok ratd v1.2.0, "rat prod on host", protocol 2026-07-28
instructions:
rat gives you persistent terminals on host: …
Tools
ok 8 tools
list_windows: List your terminals: …
ratd at host:7281 serves this client.
It calls no tool: listing windows, for instance, would create the session. Its exit code is not
zero on failure, for scripts. It warns once less than a year of validity remains.
It is written with the Client of the Go SDK and package connect, as a harness author would:
it checks that ratd stays a standard MCP server, where rat talks to ratd through a relay of its
own (see the README of cmd/rat).
Rejected: a --check mode in rat, which would test the configuration actually used by the
harness, but give the bridge a second mode. Sharing the flags gets the same result.
Later
- config: prints the configuration snippet of a harness for a client directory (the
mcpServers entry starting rat with its flags), and the full URL for harnesses connecting
directly: the harness would run exactly what the tool wrote.