How-to: share your nornir server with a friend (Tailscale)
Goal: you run one nornir-server that owns the warehouse; a friend on the same Tailscale tailnet drives it from his machine — his nornir CLI, his Claude Code agents (over MCP), and the nornir-ui viz — all reading/writing your warehouse over gRPC. See also running-as-a-server.md (systemd, the lock rule) and the live viz in design.md.
Why a server at all (the lock rule)
The warehouse is redb-backed: single writer per process. Only one process may open it directly. So exactly one nornir-server opens it; everyone else — your friend, his agents, his viz, and even your own clients — connects over gRPC instead of opening the files. That's the whole point of the thin-client split.
1. Host side (you) — start the server
# build once (server feature)
cargo build --release --features cli --bin nornir-server # in the nornir repo
# pick a strong shared secret (>=16 chars) and BIND WIDE so Tailscale can reach it
export NORNIR_SERVER_TOKEN='choose-a-long-random-secret'
export NORNIR_SERVER_ADDR='0.0.0.0:7878' # default is 127.0.0.1 = loopback-only!
export NORNIR_CONFIG=/path/to/workspace_nornir-catalog/release/nornir.toml
./target/release/nornir-server
# → "nornir-server: listening on 0.0.0.0:7878"
0.0.0.0is required for remote access — the default127.0.0.1is only reachable from your own box. Tailscale's ACL is your network firewall; the token is the app-level guard. (Per-user mTLS/ACL is a later step — for now the bearer token is shared, which is fine on a trusted tailnet.)- Find your Tailscale IP with
tailscale ip -4(e.g.100.x.y.z). Your friend useshttp://100.x.y.z:7878. A MagicDNS name (http://yourhost:7878) works too. - Keep it up across reboots with the systemd unit in running-as-a-server.md.
2. Friend side (same tailnet) — the two env vars
Everything the friend runs needs the same two variables (your Tailscale address
- the shared token):
export NORNIR_SERVER='http://100.x.y.z:7878' # your Tailscale IP / MagicDNS name
export NORNIR_SERVER_TOKEN='choose-a-long-random-secret'
Or skip the exports and just watch it:
nornir viz --server http://100.x.y.z:7878 --token 'the-shared-secret'
One line, no setup — the viz opens as a thin client and shows all your workspaces. (The token can also live in ~/.nornir/token so even --token is optional next time. See viz-modes.md.)
a) His nornir CLI (thin client)
With NORNIR_SERVER set, every warehouse-touching command routes to your server instead of opening a (nonexistent, locked) local warehouse:
nornir docs history nornir-catalog
nornir search "tpch" --corpus bench_history
nornir bench history-show nornir-catalog
b) His Claude Code agents — nornir over MCP
nornir-mcp is an MCP server (stdio) that exposes the whole nornir surface as tools; when NORNIR_SERVER is set it relays every tool to your server over gRPC. The friend registers it in Claude Code one of two ways:
Full step-by-step — including
NORNIR_WORKSPACEto scope the tools to a monitored workspace (njord/holger/…) — is in mcp-from-claude-code.md.
Option 1 — claude mcp add (CLI):
claude mcp add nornir \
--env NORNIR_SERVER=http://100.x.y.z:7878 \
--env NORNIR_SERVER_TOKEN='choose-a-long-random-secret' \
-- nornir-mcp
Option 2 — .mcp.json in his project (commit-or-not, his choice):
{
"mcpServers": {
"nornir": {
"command": "nornir-mcp",
"env": {
"NORNIR_SERVER": "http://100.x.y.z:7878",
"NORNIR_SERVER_TOKEN": "choose-a-long-random-secret"
}
}
}
}
nornir-mcp must be on his PATH (cargo install --path . --bin nornir-mcp from the nornir repo, or copy the built binary). Then in a Claude Code session the tools appear as mcp__nornir__*, and he just asks Claude Code to use them, e.g. "use the nornir MCP to show nornir-catalog's bench history" or "search the warehouse docs corpus for the mashup design." No NORNIR_SERVER set → the same binary runs embedded (and would need its own warehouse), so the env vars are what point it at your server.
c) His nornir-ui viz — pointed at your server
cargo install nornir --features viz # or: cargo build --release --features viz
NORNIR_SERVER=http://100.x.y.z:7878 NORNIR_SERVER_TOKEN='…' nornir viz
- Pick the workspace from the top-bar dropdown (it lists the server's registered workspaces and switches every tab live).
- Timeline / Dep Graph / Time Travel tabs load over
Viz.Timeline. - 📡 Live Run tab streams your active release run in real time over
Release.Progress— he watches your CI/release as it happens. - 🗄 Warehouse tab (generic table browser) works over gRPC — it lists and scans the server's tables via
Warehouse.{Tables,Scan}, so a remote client browses the same data the server owns (lock and all), scoped to the selected workspace. A 📊 chart toggle renders numeric columns as bar charts. - 🕸 Call Graph tab (same gRPC scans): pick repo + crate → force-laid-out call graph; click a node for its signature/file:line + clickable caller/callee navigation.
3. Verify / troubleshoot
NORNIR_SERVER_TOKEN must be >= 16 chars→ make the secret longer.- Friend gets connection refused → server bound to
127.0.0.1(setNORNIR_SERVER_ADDR=0.0.0.0:7878) or Tailscale isn't up on one end (tailscale status). 401 / unauthenticated→ token mismatch between host and friend.- Host viz
open warehouse failed … redb lock→ the server already owns the warehouse; stop the server to browse locally, or use the remote tabs.