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
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
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
This commit is contained in:
@@ -31,6 +31,8 @@ The codebase is split into two layers:
|
||||
|
||||
**`bcc_core.py`** — All logic with no GUI imports. Contains:
|
||||
- `Profile` / `ServerEntry` dataclasses (the data model)
|
||||
- `ClientSpec` (issue #5, cross-client) — one adapter object per MCP host capturing everything client-specific: the top-level `servers_key` (Claude uses `mcpServers`; VS Code will use `servers`), the parking `disabled_key`, config `config_filename`, the capability flags (`expands_env_refs`, `supports_restart`), and a per-server `entry_to_internal`/`entry_from_internal` translation pair (identity for Claude; the seam a differently-shaped client overrides). `CLAUDE_DESKTOP` and `CLAUDE_CODE` are the two shipped specs; `resolve_client(path)` picks one by filename, and each `Profile` carries its resolved `client`. The read/write/diff functions take an optional `spec` and default to Claude's layout, so a call with no spec is unchanged.
|
||||
|
||||
- `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/`
|
||||
- `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)
|
||||
@@ -43,7 +45,7 @@ The codebase is split into two layers:
|
||||
- `KeyValueTable` — reusable widget for env vars and headers
|
||||
- `ConnTester(QThread)` — background thread for remote reachability tests
|
||||
|
||||
**The cardinal rule**: `apply_servers()` only ever writes to `mcpServers` and `_disabledMcpServers`. All other keys in the user's config are preserved verbatim and in their original order.
|
||||
**The cardinal rule**: `apply_servers()` only ever writes the two keys the target client's servers live under — by default `mcpServers` and `_disabledMcpServers`, or whatever the profile's `ClientSpec` declares (`servers_key` + `disabled_key`). All other keys in the user's config are preserved verbatim and in their original order. The rule generalises across clients precisely because it is parameterised by the spec rather than hard-coded.
|
||||
|
||||
Disabled servers are parked under `_disabledMcpServers` (which Claude Desktop ignores) so they can be re-enabled without losing their definition.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user