Wires the maintainer's freshly-rotated signing keys into BCC (issue #68 finding 5: the old key was shared between catalog+release and had been exposed to CI), adds a key-rotation re-attestation mode to the Catalog Console so the Console can actually re-sign under the new key, and hardens the show-seed-b64 CLI prompt that led to a private key being pasted into a chat. ## New keys (#68 finding 5) - bcc_core.CATALOG_PUBKEYS -> new CATALOG pubkey (0s24PmkZcTT5yxNDdyTPHl5fyxArrHNPJKBjnXoQd8k=). Signs data/catalog.json, Console-only, offline. - .github/workflows/ci.yml EXPECTED_CATALOG_PUBKEY_B64 -> same new catalog pubkey (the CI trust anchor added for #68 finding 4). - scripts/sign_checksums.py RELEASE_PUBKEYS -> new RELEASE pubkey (6BnPgJEHJFyVltFoLTCNadIsehjy00iiW8IRlC1TfhA=). Signs SHA256SUMS only, CI-resident. - README.md 'Verifying your download' / 'Signing keys' sections filled in with both pubkeys, explicit about which key is which. The old key 082NOwVB7uURkvfyS3+knJ+40Fk6C9unsF47+2uPKo4= is retired (it was shared and exposed to CI) and is deliberately NOT retained in either trust list -- keeping a burned key in CATALOG_PUBKEYS would defeat the point of rotating it. ## Key-rotation re-attestation mode (Catalog Console) Problem: after the key swap, the existing data/catalog.json.sig (signed with the OLD key) no longer verifies under the NEW CATALOG_PUBKEYS, but catalog content is unchanged, so diff_catalogs(last_signed, current) is empty -- and can_sign()'s empty-diff guard (load-bearing, #68 finding 1) correctly refuses to sign an empty changeset. Without a rotation-aware path, the Console could never re-sign and CI would stay red forever. Fix: treat rotation as a full re-attestation, not a diff. - catalog_review.py: ReviewSession/start_review gain reattest: bool = False. When set, changes is built via diff_catalogs(None, new_catalog) -- every entry presented as if newly added, requiring a fresh acknowledgement -- instead of diffing against old_catalog. can_sign() is UNCHANGED: it still refuses a genuinely-empty changeset and still enforces the TOCTOU blob-SHA pin and the blocking-risk check, because reattest sessions simply never produce an empty changeset (unless the catalog itself is empty). - catalog_console.py: adds catalog_signature_valid_at(repo_dir, commit, raw), which calls bcc_core.verify_catalog_signature directly (never reimplemented) to detect whether the committed .sig verifies under the CURRENT CATALOG_PUBKEYS. ReviewWindow._on_load's source=main path uses this to decide reattest=True/False, and shows a loud, explicit red banner ("KEY ROTATION IN PROGRESS...") whenever reattest mode is entered -- never silent. Status text and Sign-button gating flow through the same can_sign()/all_entries_acknowledged() path as normal review. - tests/test_catalog_review.py: 6 new tests covering re-attest mode (one change-entry per server, gating until all acknowledged, then permits), confirming normal mode still refuses an empty diff (rotation path is not a general bypass), and confirming reattest mode still enforces the blocking-risk check and the TOCTOU pin. ## show-seed-b64 hardening (Task 3) The maintainer ran show-seed-b64 --release, saw an ambiguous prompt, and pasted the printed PRIVATE seed into a chat believing it was public. - cmd_show_seed_b64: passphrase prompt is now explicit ("Passphrase for the release signing key (the one YOU chose when generating it)"). A loud three-line warning banner prints to STDERR immediately before the seed ("!!! PRIVATE KEY BELOW..."); the seed itself stays alone on STDOUT so piping into pbcopy or a CI secret field still works cleanly. - cmd_keygen: labels for both key kinds now say PUBLIC/PRIVATE explicitly and state safety properties inline (safe to commit vs. never commit), so the printed output can't be mistaken for the other key's. ## Verification - ruff check . / ruff format --check .: clean. - pytest: full suite green (360 passed, 1 skipped). - Delete-the-check-and-watch-it-fail: each of can_sign()'s four gates (empty-diff, TOCTOU pin, blocking-risk, acknowledge-all) and the reattest branch itself were individually removed and confirmed to turn a test red, then restored -- see PR description for the exact failures. Not touched: data/catalog.json (content-signed, maintainer's job via the Console). No private key generated, requested, or committed. bcc.py untouched (open PR #67).
Better Claude Config (BCC)
A small, cross-platform GUI for editing the mcpServers block of Claude Desktop
and Claude Code installs — without ever hand-writing JSON.
BCC only ever touches mcpServers (and its own _disabledMcpServers parking
key). Every other key in your config is preserved verbatim, in its original
order. Each write is atomic and makes a timestamped backup first.
Download (no Python required)
Pre-built self-contained binaries are attached to every GitHub Release:
| Platform | Download | Run |
|---|---|---|
| macOS | BetterClaudeConfig-macOS.zip |
Unzip → drag BetterClaudeConfig.app to Applications |
| Windows | BetterClaudeConfig-Windows.zip |
Unzip → double-click BetterClaudeConfig.exe |
| Linux | BetterClaudeConfig-Linux.tar.gz |
Extract → run BetterClaudeConfig |
macOS Gatekeeper note: the app is not notarized. On first launch, right-click → Open, or run
xattr -cr /Applications/BetterClaudeConfig.appin a terminal.
Verifying your download
BCC isn't code-signed — there's no budget for a paid certificate (macOS
Developer ID, Windows Authenticode). Instead, every release publishes a
SHA256SUMS file listing the checksum of each archive, detached-signed with
Ed25519 as SHA256SUMS.sig. Both are attached to the release alongside the
binaries.
What this proves: the file you downloaded is byte-for-byte what we published, and the manifest itself was signed by our release key.
What this does NOT do: it does not make the binary "safe," and it does not remove the macOS Gatekeeper or Windows SmartScreen warning — those are only suppressed by a paid OS-vendor certificate, which this project doesn't have. Verifying checksums is about detecting tampering in transit or on a mirror, not about vouching for the software.
This manifest is signed with BCC's release key, which is a different
key from the one that signs the MCP server catalog — see
Signing keys below for why, and for the public key value
to use with --pubkey-b64 below.
macOS / Linux
# From inside the folder you downloaded the release files into:
sha256sum -c SHA256SUMS
If your sha256sum complains about missing files, download SHA256SUMS
into the same directory as the archive you downloaded — it lists every
platform's archive, and only the one(s) present will be checked.
To also verify the manifest's signature (optional, requires Python +
pip install cryptography and a checkout of this repo):
python3 scripts/sign_checksums.py verify \
--sums SHA256SUMS --sig SHA256SUMS.sig \
--pubkey-b64 "<the release public key from Signing keys, below>"
Windows (PowerShell)
Get-FileHash .\BetterClaudeConfig-Windows.zip -Algorithm SHA256
Compare the printed hash (case-insensitively) against the matching line in
SHA256SUMS.
If a release has no SHA256SUMS.sig
The signing key is a repo secret that has to be configured manually; if a
release is missing the .sig file, the checksums themselves are still
valid and safe to check against — the release workflow only skips signing,
never checksum generation.
Signing keys
BCC uses two separate Ed25519 keypairs, deliberately never the same key, because they protect different things and live in different places:
| Catalog key | Release key | |
|---|---|---|
| Signs | data/catalog.json (the MCP server catalog every user's app trusts) |
SHA256SUMS (the checksum manifest for release binaries) |
| Verified by | bcc_core.CATALOG_PUBKEYS |
scripts/sign_checksums.RELEASE_PUBKEYS |
| Lives | Offline, passphrase-encrypted, maintainer's machine only (OS keychain or an encrypted file outside the repo — see the Catalog Console, issue #62) | A Gitea Actions repo secret, RELEASE_SIGNING_KEY — intentionally CI-resident |
| Generated with | python catalog_console.py keygen |
python catalog_console.py keygen --release |
| Exported for CI with | (never — there is no supported way to export this key) | python catalog_console.py show-seed-b64 --release |
Why two keys: the catalog key is the root of trust for what BCC
actually executes on a user's machine — every command/args pair in
the shipped catalog is only there because this key signed it. If that key
and the release-checksum key were the same (as they briefly were — see
issue #68), then anything that can exfiltrate a Gitea
Actions secret (a malicious workflow-file PR, a compromised runner, a leaky
log) could sign a catalog every user's copy of BCC would trust, not just a
checksum manifest. Splitting them means a CI/secret compromise burns the
release key, never the catalog key — checksums for a future release could
be forged, which is bad, but no attacker gains the ability to make BCC run
arbitrary commands on installs that trust the catalog. That asymmetry is
the entire point of having two keys instead of one.
The catalog key is never meant to leave the maintainer's machine: it's
generated, stored, unlocked, and used to sign entirely inside the Catalog
Console (catalog_console.py), and catalog_console.py show-seed-b64
refuses to run without --release specifically so the catalog seed can't
be exported by habit or muscle memory.
Release signing public key (Ed25519, base64, raw 32 bytes) — this is
the RELEASE key, which signs SHA256SUMS (release checksums). It does
not sign data/catalog.json and is not the key bcc_core.CATALOG_PUBKEYS
trusts:
6BnPgJEHJFyVltFoLTCNadIsehjy00iiW8IRlC1TfhA=
The catalog public key (Ed25519, base64, raw 32 bytes) — this is the key
that signs data/catalog.json and is trusted via bcc_core.CATALOG_PUBKEYS
and the CI trust anchor in .github/workflows/ci.yml. It is listed here
for completeness, not because you need it to verify a download — use the
release key above for that:
0s24PmkZcTT5yxNDdyTPHl5fyxArrHNPJKBjnXoQd8k=
Both keys above were rotated 2026-07 — see issue #68 finding 5. The prior (shared) key is retired and is deliberately not kept in either trust list; retaining a burned key would defeat the point of rotating it.
Run from source
pip install -r requirements.txt
python bcc.py
(Python 3.10+, PySide6 6.6+.)
What it does
-
Auto-discovers installs — scans the platform's app-support folder for any
Claude*directory (soClaudeandClaude-Workboth show up) and finds Claude Code's user-scope config at~/.claude.json(the fileclaude mcp addwrites). Use Add config… to point at any other file manually — e.g. a project's.mcp.json. -
Form-based editing — name, command, args (one per line), env vars, or for remote servers: URL, transport, and headers. No raw JSON.
-
Paste JSON — even broken JSON — drop in any snippet from an MCP doc (full
mcpServersblock, inner map, or a single bare server object); it's parsed and merged. The paste box parses as you type and auto-repairs the stuff docs and chat windows love to break: markdown fences, surrounding prose, comments, trailing or missing commas, smart quotes, single quotes, unquoted keys, and unclosed braces — and tells you exactly what it fixed before you commit. -
Drag & drop a
.jsonfile onto the window to import servers from it. -
Copy to ▸ — copy the selected server straight into your other install.
-
Active / Disabled sections — servers are shown in two labelled lists with live counts, so a server that's switched off in the config is obvious at a glance. Toggle a server's checkbox to move it between sections (disabled servers are parked under
_disabledMcpServers, which Claude ignores). -
Dependency check + diagnostics — each server shows whether its
commandis actually found on PATH:- ● green — found on the normal PATH; will work anywhere.
- ▲ amber (PATH-risk) — found, but only in a location BCC added on top of
the inherited PATH. It'll work when you launch Claude from a terminal, but a
double-clicked
.appmay not see it. This is the usual cause of "the tool says it's fine but Claude won't start the server." One click on Use full path ↳ rewrites the bare command (npx) to its absolute path so any launch finds it. - ● red (broken) — not found anywhere.
- ◆ blue — remote (url) server. Test connection does a live reachability check on a background thread (any HTTP response = reachable).
Each section header shows a broken / PATH-risk count badge, and selecting a flagged server auto-opens a Details panel with a full, copy-pasteable report: which command resolved to what, tailored install hints, and the exact PATH that was searched (with
+marking the dirs BCC added). Re-check re-scans PATH after you install something; Copy diagnostics grabs the whole report for a bug report.
After saving, restart that Claude install for changes to take effect.
Config locations it scans
Claude Desktop
| OS | Base it scans for Claude* |
|---|---|
| macOS | ~/Library/Application Support |
| Windows | %APPDATA% |
| Linux | ~/.config |
Claude Code (all platforms): ~/.claude.json (user scope). If servers are
found parked in ~/.claude/settings.json — where Claude Code ignores them — that
file is also listed, marked legacy, so you can copy them over.
Files
bcc.py— the GUI.bcc_core.py— all the file/JSON/validation/dependency logic (no GUI deps).test_core.py— unit suite for the core (python test_core.py).bcc.spec— PyInstaller build spec (cross-platform).scripts/build_icons.py— regeneratesicons/app.icnsandicons/app.icofrom source PNGs.scripts/sign_checksums.py— generates and Ed25519-signs the releaseSHA256SUMSmanifest (see Verifying your download).catalog_console.py/catalog_review.py— maintainer-only, never shipped to users (excluded frombcc.spec; seetests/test_catalog_console_packaging.py). The Catalog Console: review + signdata/catalog.json, and generate/manage both signing keys (keygen,keygen --release) — see Signing keys.
Building from source
pip install -r requirements-dev.txt
python scripts/build_icons.py # regenerate icons if needed
pyinstaller bcc.spec
# macOS → dist/BetterClaudeConfig.app
# Windows → dist/BetterClaudeConfig.exe
# Linux → dist/BetterClaudeConfig
Releases are built automatically by GitHub Actions (.github/workflows/release.yml) when a version tag is pushed:
git tag v1.0.0 && git push --tags
Rebrand
The accent color is one constant (ACCENT) at the top of bcc.py.
License
MIT — see LICENSE.