Cross-client phase 1: ClientSpec adapter, no behavior change (#5) #85

Merged
the_og merged 2 commits from feat/5-client-adapter-refactor into main 2026-08-03 23:19:56 -04:00
Owner

Refs #5 — phase 1 of cross-client support (Cursor / Windsurf / VS Code). This is the keystone refactor with no behavior change, per the phased plan on the issue: land the adapter model first with Claude Desktop + Claude Code as the two adapters, so that adding real clients afterward is a small, isolated, testable change instead of chasing Claude's hard-coded layout through the whole codebase.

The problem it removes

BCC hard-coded Claude's layout in three ways that every other client differs on:

  • servers always live under the literal key mcpServers (VS Code uses servers),
  • a fixed set of Claude file locations,
  • "which client is this?" answered by sniffing a filename (which also gated ${VAR} expansion and the Restart action).

What phase 1 adds

  • ClientSpec (frozen dataclass): servers_key, disabled_key, config_filename, expands_env_refs, supports_restart, and an entry_to_internal / entry_from_internal translation pair. Those two methods are the entire extension point — identity for any mcpServers-shaped client, and the one place a differently-shaped client (VS Code's per-server type/inputs) will override.
  • CLAUDE_DESKTOP and CLAUDE_CODE specs. Both use mcpServers + the existing parking key, so their translation is the identity and nothing changes for today's users. resolve_client(path) reproduces the old filename rule exactly; every Profile now carries its resolved .client.
  • The pipeline is parameterized, defaulted to Claude's layout. extract_servers / apply_servers / _server_sections / external_change_summary take an optional spec and default to the pre-refactor keys, so every existing call site and test that omits a spec is byte-for-byte unchanged. The cardinal rule now generalizes: apply_servers only ever writes the client's own two keys — parameterized, not hard-coded.
  • One source of truth for client identity. profile_targets_claude_desktop and client_expands_env_refs are now thin reads off profile.client, with identical answers to before.
  • GUI: the load, Copy-to, save, and stale-merge paths pass the profile's spec into the core calls. Mechanical — no logic moved into bcc.py.

Proof the seam works

Beyond preserving the existing suite (behavior preservation), the tests add a synthetic non-mcpServers client — "servers" key, a different disabled key, and a per-server type field the internal model doesn't carry. It runs through the full pipeline (extract → apply → masking → external-change diff) and proves the abstraction generalizes before any real client depends on it. This is the "make sure the idea works" check.

Tests / CI

+14 tests; 458 passed, 1 skipped; ruff check + format clean. Touches no catalog/key files, so the catalog-signature gate is unaffected.

Verification limits

Per the last several PRs: CI has no PySide6, so bcc.py is untestable by the suite — all decision logic lives in bcc_core and is tested there. The GUI edits are pure parameter-passing into already-tested functions. Worth a human eyeball that loading/saving a Claude Desktop and a Claude Code profile still behaves identically.

Not in scope (phase 2+)

No new clients yet. Cursor + Windsurf (same mcpServers shape, new discovery paths) and VS Code (the servers key + type/inputs translation via a real override of the seam this PR introduces) are the follow-ups.

Refs #5 — phase 1 of cross-client support (Cursor / Windsurf / VS Code). This is the **keystone refactor with no behavior change**, per the phased plan on the issue: land the adapter model first with Claude Desktop + Claude Code as the two adapters, so that adding real clients afterward is a small, isolated, testable change instead of chasing Claude's hard-coded layout through the whole codebase. ## The problem it removes BCC hard-coded Claude's layout in three ways that every other client differs on: - servers always live under the literal key `mcpServers` (VS Code uses `servers`), - a fixed set of Claude file locations, - "which client is this?" answered by sniffing a filename (which also gated `${VAR}` expansion and the Restart action). ## What phase 1 adds - **`ClientSpec`** (frozen dataclass): `servers_key`, `disabled_key`, `config_filename`, `expands_env_refs`, `supports_restart`, and an `entry_to_internal` / `entry_from_internal` translation pair. Those two methods are the **entire extension point** — identity for any `mcpServers`-shaped client, and the one place a differently-shaped client (VS Code's per-server `type`/`inputs`) will override. - **`CLAUDE_DESKTOP`** and **`CLAUDE_CODE`** specs. Both use `mcpServers` + the existing parking key, so their translation is the identity and **nothing changes for today's users**. `resolve_client(path)` reproduces the old filename rule exactly; every `Profile` now carries its resolved `.client`. - **The pipeline is parameterized, defaulted to Claude's layout.** `extract_servers` / `apply_servers` / `_server_sections` / `external_change_summary` take an optional `spec` and default to the pre-refactor keys, so every existing call site and test that omits a spec is byte-for-byte unchanged. The **cardinal rule now generalizes**: `apply_servers` only ever writes the client's own two keys — parameterized, not hard-coded. - **One source of truth for client identity.** `profile_targets_claude_desktop` and `client_expands_env_refs` are now thin reads off `profile.client`, with identical answers to before. - **GUI:** the load, Copy-to, save, and stale-merge paths pass the profile's spec into the core calls. Mechanical — no logic moved into `bcc.py`. ## Proof the seam works Beyond preserving the existing suite (behavior preservation), the tests add a **synthetic non-`mcpServers` client** — `"servers"` key, a different disabled key, and a per-server `type` field the internal model doesn't carry. It runs through the full pipeline (extract → apply → masking → external-change diff) and proves the abstraction generalizes *before* any real client depends on it. This is the "make sure the idea works" check. ## Tests / CI +14 tests; **458 passed, 1 skipped**; ruff check + format clean. Touches no catalog/key files, so the catalog-signature gate is unaffected. ## Verification limits Per the last several PRs: CI has no PySide6, so `bcc.py` is untestable by the suite — all decision logic lives in `bcc_core` and is tested there. The GUI edits are pure parameter-passing into already-tested functions. Worth a human eyeball that loading/saving a Claude Desktop and a Claude Code profile still behaves identically. ## Not in scope (phase 2+) No new clients yet. Cursor + Windsurf (same `mcpServers` shape, new discovery paths) and VS Code (the `servers` key + `type`/inputs translation via a real override of the seam this PR introduces) are the follow-ups.
the_og added 1 commit 2026-08-03 22:54:16 -04:00
refactor: introduce ClientSpec adapter; route Claude Desktop + Code through it (#5)
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 24s
CI / Lint (ruff) (pull_request) Successful in 21s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 32s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 31s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 35s
CI / Catalog signature (pull_request) Successful in 26s
4a95b370b9
Cross-client support (Cursor / Windsurf / VS Code) was blocked on Claude's
layout being hard-coded throughout the code: servers always under the literal
"mcpServers" key, a fixed set of Claude file locations, and "which client is
this?" answered by sniffing a filename. Adding a client that differs on any of
those axes meant chasing those assumptions through a dozen sites.

This is phase 1 of #5: the keystone refactor, with NO behaviour change. It adds
a ClientSpec adapter that captures the three things that vary across clients --
the top-level servers key, the config discovery paths, and the per-server value
shape -- plus the capability flags that were previously computed inline from a
filename (does the client expand ${VAR}? can we offer Restart?).

- ClientSpec (frozen dataclass): servers_key, disabled_key, config_filename,
  expands_env_refs, supports_restart, and entry_to_internal/entry_from_internal
  -- the per-server translation seam, identity for any mcpServers-shaped client,
  the single point a differently-shaped client (VS Code's type/inputs form)
  overrides.
- CLAUDE_DESKTOP and CLAUDE_CODE specs; both use mcpServers + the existing
  parking key, so their translation is the identity and nothing changes for
  today's users. resolve_client(path) reproduces the old filename rule exactly;
  each Profile now carries its resolved .client.
- extract_servers / apply_servers / _server_sections / external_change_summary
  take an optional spec and default to Claude's layout, so every existing call
  site and test that omits a spec is byte-for-byte unchanged. The cardinal rule
  now generalises: apply_servers only ever writes the client's own two keys,
  parameterised rather than hard-coded.
- profile_targets_claude_desktop and client_expands_env_refs are now thin reads
  off the profile's spec -- one source of truth for client identity instead of
  scattered filename checks -- with identical answers.
- GUI: the load, Copy-to, save and stale-merge paths pass the profile's spec
  into the core calls. Mechanical; no logic moved into bcc.py (which CI can't
  test -- no PySide6).

Tests: +14. Existing suite unchanged and green (behaviour preservation). A
synthetic non-mcpServers spec ("servers" key, a different disabled key, a
per-server `type` field) exercises the whole pipeline -- extract, apply,
masking, external-change diff -- proving the seam actually generalises before
any real client depends on it. 458 passed, 1 skipped; ruff clean.

Refs #5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwBecy6N83jnqQmw8ezwE
the_og added 1 commit 2026-08-03 23:17:12 -04:00
fix: declare cryptography as a runtime dependency
CI / Lint (ruff) (pull_request) Successful in 24s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 38s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 28s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 31s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 28s
CI / Catalog signature (pull_request) Successful in 19s
57fd3cb6e3
bcc_core imports cryptography at module load (catalog signature
verification), so it's required to even start the app -- but
requirements.txt listed only PySide6, so a from-source run died with
ModuleNotFoundError: No module named 'cryptography'. The frozen release
builds were unaffected because PyInstaller follows the import, which is
why this never surfaced until someone ran the GUI from source to review
this branch.

Move cryptography into requirements.txt (runtime) and re-label it in
requirements-dev.txt as runtime rather than test-only; the version pin is
unchanged (>=42.0).

Refs #5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwBecy6N83jnqQmw8ezwE
the_og merged commit 5add9b0ce0 into main 2026-08-03 23:19:56 -04:00
the_og deleted branch feat/5-client-adapter-refactor 2026-08-03 23:19:56 -04:00
Sign in to join this conversation.