Skip to main content

Set up a client machine

Each machine that runs Claude Code sessions needs a checkout of this repo (for the helper script and the shim), its own token, and two MCP entries. The tested path keeps the token in 1Password.

1. Check out and build​

git clone https://github.com/catesworks/session-bus.git ~/src/session-bus
cd ~/src/session-bus
corepack enable
pnpm install --frozen-lockfile && pnpm run build

Use a stable location. The MCP entries hold absolute paths into it.

2. Mint a token on the bus host and store it in 1Password​

On the bus host, once per client machine. The token goes from token mint straight into op item create through a pipe. It never appears in argv or in a shell variable.

node dist/cli.js token mint --principal me --scopes bus:read,bus:write --label laptop \
| node -e 'let t="";process.stdin.on("data",d=>t+=d).on("end",()=>process.stdout.write(JSON.stringify({title:"session-bus/laptop",category:"API_CREDENTIAL",fields:[{id:"SESSION_BUS_TOKEN",label:"SESSION_BUS_TOKEN",type:"CONCEALED",value:t.trim()}]})))' \
| op item create --vault <vault>
  • Pass the JSON on stdin and do not add --template. op item create --template - fails with "cannot create an item from template and stdin at the same time".
  • Name the field exactly SESSION_BUS_TOKEN.
  • Note the item id that op prints. Use the id, not the title, in op:// references. A title containing / breaks op:// lookups.
  • If your default op login cannot see the vault, prefix the command with OP_SERVICE_ACCOUNT_TOKEN="$<VAR>", where <VAR> holds a service-account token that can.

3. Add the entries​

On the client machine:

scripts/setup-client.sh --url https://<bus-host>.<tailnet>.ts.net:8443 --item <item-id> --vault <vault> --op-sa-var ''

Preview first with --dry-run. It prints two entries:

bus {"type":"http","url":"https://<bus-host>.<tailnet>.ts.net:8443/mcp","headersHelper":"SESSION_BUS_OP_REF=op://<vault>/<item-id>/SESSION_BUS_TOKEN /path/to/session-bus/scripts/bus-headers.sh"}
bus-push {"type":"stdio","command":"/path/to/node","args":["/path/to/session-bus/dist/cli.js","channel"],"env":{"BUS_URL":"https://<bus-host>.<tailnet>.ts.net:8443","SESSION_BUS_TOKEN_CMD":"op read op://<vault>/<item-id>/SESSION_BUS_TOKEN"}}
  • bus is the pull entry. Its headersHelper runs through a shell each time Claude Code connects, so the op:// reference is resolved then. The config holds no secret.
  • bus-push is the push shim. Without a session name it idles, so it is safe in every claude.
  • With --op-sa-var <VAR> instead of '', both commands are prefixed with OP_SERVICE_ACCOUNT_TOKEN="$<VAR>". The variable name stays literal in the config and is expanded when the command runs.
  • --allow-tools adds mcp__bus to permissions.allow, so bus tool calls do not prompt.

The script then checks that both entries are Connected and that this prints 0:

grep -c sbus_ ~/.claude.json

In /mcp, bus should show 7 tools.

Never store the expanded token​

Do not use claude mcp add ... -e SESSION_BUS_TOKEN=<token> or --header "Authorization: Bearer <token>". Both write the token into ~/.claude.json and your shell history.

If you cannot use 1Password, put ${SESSION_BUS_TOKEN} (the literal text) in a project .mcp.json and export the variable in the shell that launches claude. Whether user-scope entries expand ${VAR} has not been tested.

The skill​

skills/session-bus/ teaches a session how to pick a name, send, read, ack, and treat incoming messages as untrusted. Install it with a symlink:

ln -s "$PWD/skills/session-bus" ~/.claude/skills/session-bus

Its allowed-tools lets the 7 bus tools run without prompting while the skill is active. The same skill is available as /sb:session-bus once this repo is added as a Claude Code plugin marketplace.

Second machine checklist​

  1. Network: the machine can reach the bus (an unauthenticated POST returns 401):
    curl -s -o /dev/null -w '%{http_code}\n' -X POST https://<bus-host>.<tailnet>.ts.net:8443/mcp
  2. Own token item, own setup-client.sh run.
  3. A send from the first machine appears in this machine's inbox.