fix: point Claude Code profile at ~/.claude.json, not settings.json
Claude Code stores user-scope MCP servers in ~/.claude.json (what 'claude mcp add' writes); ~/.claude/settings.json is for permissions and hooks and rejects an mcpServers key with a schema error, so BCC was reading (and writing) servers where Claude Code never looks. If servers are found parked in settings.json, that file is still listed as 'Claude Code (legacy settings.json)' so they can be copied into the real config via Copy to. Docs updated; verified against docs.claude.com/en/docs/claude-code/settings.
This commit is contained in:
@@ -31,7 +31,7 @@ The codebase is split into two layers:
|
|||||||
|
|
||||||
**`bcc_core.py`** — All logic with no GUI imports. Contains:
|
**`bcc_core.py`** — All logic with no GUI imports. Contains:
|
||||||
- `Profile` / `ServerEntry` dataclasses (the data model)
|
- `Profile` / `ServerEntry` dataclasses (the data model)
|
||||||
- `discover_profiles()` — scans the platform's app-support directory for `Claude*` folders (Claude Desktop) **and** always adds `~/.claude/settings.json` (Claude Code, cross-platform)
|
- `discover_profiles()` — scans the platform's app-support directory for `Claude*` folders (Claude Desktop) **and** always adds `~/.claude.json` (Claude Code user scope — what `claude mcp add` writes). `~/.claude/settings.json` is NOT a server config (it rejects `mcpServers` with a schema error) and is only surfaced, labelled legacy, if servers are found parked in it. Project-scope `.mcp.json` files can be opened via Add config…
|
||||||
- `load_config` / `extract_servers` / `apply_servers` / `write_config` — the read/write pipeline; writes are atomic with rotating timestamped backups in `.bcc_backups/`
|
- `load_config` / `extract_servers` / `apply_servers` / `write_config` — the read/write pipeline; writes are atomic with rotating timestamped backups in `.bcc_backups/`
|
||||||
- `parse_pasted_json()` / `parse_pasted_json_verbose()` — accepts three JSON shapes (full config, inner map, or bare server object). Input does not have to be valid JSON: `repair_json_text()` auto-fixes markdown fences, surrounding prose, `//` `/* */` `#` comments, trailing/missing commas, smart quotes, single quotes, unquoted keys, Python/JS literals, and unclosed braces. The verbose variant also returns human-readable notes describing every repair applied (shown live in the paste dialog)
|
- `parse_pasted_json()` / `parse_pasted_json_verbose()` — accepts three JSON shapes (full config, inner map, or bare server object). Input does not have to be valid JSON: `repair_json_text()` auto-fixes markdown fences, surrounding prose, `//` `/* */` `#` comments, trailing/missing commas, smart quotes, single quotes, unquoted keys, Python/JS literals, and unclosed braces. The verbose variant also returns human-readable notes describing every repair applied (shown live in the paste dialog)
|
||||||
- `check_dependency()` / `diagnostics_text()` / `pin_command_path()` — PATH resolution logic; distinguishes "found on normal PATH" (ok) vs "found only on augmented PATH" (warn) vs "not found" (missing). Uses an `lru_cache`-memoized `augmented_path()` that extends the inherited PATH with common runtime locations (nvm, homebrew, cargo, volta, etc.)
|
- `check_dependency()` / `diagnostics_text()` / `pin_command_path()` — PATH resolution logic; distinguishes "found on normal PATH" (ok) vs "found only on augmented PATH" (warn) vs "not found" (missing). Uses an `lru_cache`-memoized `augmented_path()` that extends the inherited PATH with common runtime locations (nvm, homebrew, cargo, volta, etc.)
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ python bcc.py
|
|||||||
|
|
||||||
- **Auto-discovers installs** — scans the platform's app-support folder for any
|
- **Auto-discovers installs** — scans the platform's app-support folder for any
|
||||||
`Claude*` directory (so `Claude` and `Claude-Work` both show up) **and** finds
|
`Claude*` directory (so `Claude` and `Claude-Work` both show up) **and** finds
|
||||||
Claude Code at `~/.claude/settings.json`. Use **Add config…** to point at any
|
Claude Code's user-scope config at `~/.claude.json` (the file `claude mcp add`
|
||||||
other file manually.
|
writes). 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
|
- **Form-based editing** — name, command, args (one per line), env vars, or for
|
||||||
remote servers: URL, transport, and headers. No raw JSON.
|
remote servers: URL, transport, and headers. No raw JSON.
|
||||||
- **Paste JSON — even broken JSON** — drop in any snippet from an MCP doc (full
|
- **Paste JSON — even broken JSON** — drop in any snippet from an MCP doc (full
|
||||||
@@ -80,7 +81,9 @@ After saving, **restart that Claude install** for changes to take effect.
|
|||||||
| Windows | `%APPDATA%` |
|
| Windows | `%APPDATA%` |
|
||||||
| Linux | `~/.config` |
|
| Linux | `~/.config` |
|
||||||
|
|
||||||
**Claude Code** (all platforms): `~/.claude/settings.json`
|
**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
|
## Files
|
||||||
|
|
||||||
|
|||||||
+20
-4
@@ -87,7 +87,10 @@ def discover_profiles() -> list[Profile]:
|
|||||||
|
|
||||||
Claude Desktop: scans the platform app-support folder for any `Claude*`
|
Claude Desktop: scans the platform app-support folder for any `Claude*`
|
||||||
directory (catches `Claude`, `Claude-Work`, etc.).
|
directory (catches `Claude`, `Claude-Work`, etc.).
|
||||||
Claude Code: always at ~/.claude/settings.json on every platform.
|
Claude Code: user-scope MCP servers live in ~/.claude.json (that's what
|
||||||
|
`claude mcp add` writes; project scope is a per-repo .mcp.json, which can
|
||||||
|
be opened via 'Add config…'). NOT ~/.claude/settings.json — that file is
|
||||||
|
for permissions/hooks and rejects an mcpServers key with a schema error.
|
||||||
"""
|
"""
|
||||||
base = app_support_base()
|
base = app_support_base()
|
||||||
out: list[Profile] = []
|
out: list[Profile] = []
|
||||||
@@ -99,11 +102,24 @@ def discover_profiles() -> list[Profile]:
|
|||||||
cfg = d / CONFIG_FILENAME
|
cfg = d / CONFIG_FILENAME
|
||||||
out.append(Profile(label=d.name, path=cfg, config_exists=cfg.is_file()))
|
out.append(Profile(label=d.name, path=cfg, config_exists=cfg.is_file()))
|
||||||
|
|
||||||
# Claude Code global settings (~/.claude/settings.json) — same mcpServers format,
|
home = Path.home()
|
||||||
# cross-platform (the ~/.claude/ directory is always in the same place).
|
cc_cfg = home / ".claude.json"
|
||||||
cc_cfg = Path.home() / ".claude" / "settings.json"
|
|
||||||
out.append(Profile(label="Claude Code", path=cc_cfg, config_exists=cc_cfg.is_file()))
|
out.append(Profile(label="Claude Code", path=cc_cfg, config_exists=cc_cfg.is_file()))
|
||||||
|
|
||||||
|
# Legacy: earlier BCC versions (and hand-edits) may have parked servers in
|
||||||
|
# ~/.claude/settings.json, where Claude Code ignores them. Surface that
|
||||||
|
# file only when it actually contains an mcpServers block, so the user can
|
||||||
|
# Copy to ▸ the entries into the real config.
|
||||||
|
legacy = home / ".claude" / "settings.json"
|
||||||
|
if legacy.is_file():
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
if "mcpServers" in load_config(legacy):
|
||||||
|
out.append(
|
||||||
|
Profile(
|
||||||
|
label="Claude Code (legacy settings.json)", path=legacy, config_exists=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,34 @@ def test_flags_missing_config_file(fake_base):
|
|||||||
assert any(p.label == "Claude-Work" and not p.config_exists for p in profs)
|
assert any(p.label == "Claude-Work" and not p.config_exists for p in profs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_home(tmp_path, monkeypatch, fake_base):
|
||||||
|
home = tmp_path / "home"
|
||||||
|
(home / ".claude").mkdir(parents=True)
|
||||||
|
monkeypatch.setattr(c.Path, "home", lambda: home)
|
||||||
|
return home
|
||||||
|
|
||||||
|
|
||||||
|
def test_claude_code_uses_dot_claude_json(fake_home):
|
||||||
|
(fake_home / ".claude.json").write_text('{"mcpServers": {"x": {"command": "npx"}}}')
|
||||||
|
profs = c.discover_profiles()
|
||||||
|
cc = [p for p in profs if p.label == "Claude Code"]
|
||||||
|
assert len(cc) == 1
|
||||||
|
assert cc[0].path == fake_home / ".claude.json"
|
||||||
|
assert cc[0].config_exists
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_settings_json_surfaced_only_with_servers(fake_home):
|
||||||
|
# settings.json without mcpServers -> not listed
|
||||||
|
(fake_home / ".claude" / "settings.json").write_text('{"permissions": {}}')
|
||||||
|
assert not any("legacy" in p.label for p in c.discover_profiles())
|
||||||
|
# settings.json WITH parked mcpServers -> listed so the user can migrate
|
||||||
|
(fake_home / ".claude" / "settings.json").write_text(
|
||||||
|
'{"mcpServers": {"old": {"command": "npx"}}}'
|
||||||
|
)
|
||||||
|
assert any("legacy" in p.label for p in c.discover_profiles())
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# 2. Write pipeline: preserves other keys + order, only touches mcpServers
|
# 2. Write pipeline: preserves other keys + order, only touches mcpServers
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|||||||
Reference in New Issue
Block a user