Skip to content
NewMCP secrets on two Macs: Bitwarden fills the gaps, the terminal reminds me
Nov 4, 2026·4 MIN READ
MCP secrets on two Macs: Bitwarden fills the gaps, the terminal reminds me

MCP secrets on two Macs: Bitwarden fills the gaps, the terminal reminds me

My Claude Code setup syncs through git, but MCP tokens can't. Now Bitwarden holds the values, make pull fills the empty ones, and a new terminal tells me what is still missing.

Today I ran make pull on my older Mac after a round of changes on the new one. Skills, settings and the list of MCP servers all came across. Then the output ended with four lines like this:

make pull output
SECRET    slack.env.SLACK_TOKEN is not set on this machine, fill it in ~/.claude.json
SECRET    bank-statements.env.STATEMENTS_DIR is not set on this machine, fill it in ~/.claude.json

The sync did its job. It knew a token was needed and left it blank on purpose, because secrets never go into the repo. But the values only existed on the other Mac, and the only reminder was a line in the output that I would scroll past. A week later I would open Claude Code, ask for the Slack tool, and wonder why it was broken.

I set up the two-Mac sync last week. This is the missing piece.

The rule: the repo knows the names, Bitwarden knows the values

I had just moved every password I own into Bitwarden and turned off Chrome and iCloud passwords. So the answer was already on my machine: one vault, one place for every value, on every computer and the phone.

flowchart LR
    A["mcp-servers.json<br/>(git, names only)"] --> B[make pull]
    C["Bitwarden<br/>folder claude-mcp"] --> B
    B --> D["~/.claude.json<br/>(this Mac)"]
    D -- "set only here" --> C
    B --> E["state/todo.txt"]
    E --> F[new terminal]

The vault has one folder, claude-mcp, with one secure note per MCP server. Each env variable is a hidden field on that note. For every blank key, the sync script now does one of four things:

This MacBitwardenWhat happens
emptyhas itcopied to ~/.claude.json
has itemptyuploaded to Bitwarden
has itdifferentflagged, I fix it by hand
emptyemptyadded to the to-do list

The upload row matters most. When I add a new MCP server and paste its token on one Mac, the next make push stores it in the vault. The other Mac picks it up on its next pull. I never have to remember to copy it.

Reminders where I actually look

My first idea was a GitHub issue per missing secret. It sounded tidy, but nothing would close the issue when I set the value, so it would go stale in a week. A local file always matches the real state of the machine.

So the script writes whatever is still open to ~/.claude/state/todo.txt, and one line in ~/.zshrc prints it in yellow when I open a terminal. An empty file prints nothing. That covers Claude Code and Codex, because both start from a terminal.

I first wanted a Claude Code SessionStart hook for the reminder. But my settings.json is gitignored, so the hook would never reach the other Mac. The zsh line is a tracked script plus one line per machine, and the to-do list itself reminds me to add that line.

When the vault is locked, the script can't read Bitwarden. It still checks what is empty on this Mac and prints the hint run make secrets. That target unlocks the vault, asks for the master password once, and runs the full sync.

The bug was in my CLI, not my code

Before the first real run I checked bw status. The Bitwarden CLI on this Mac was still logged in to an old account on the EU server, which I had already deleted. Every secret would have gone into a vault that no longer existed. The fix was three commands: bw logout, bw config server, bw login.

The CLI keeps its own login, separate from the app and the browser extension. After you move to a new Bitwarden account, check every tool that talks to the vault, not just the ones you can see.

What it took

About 150 lines of Python in the existing sync script, one make target and a five-line zsh file. The part that took thinking was the table above, especially what to do when both sides have a value and they differ. I don't guess there. The script flags it and I pick one. Next time I'd start with bw status on every machine before writing a line of code.

Share
Mariusz Smenżyk
Written by

Mariusz Smenżyk

Builder for sport. Make Things Live.

Related notes