Merge pull request 'feat: named server sets — save/apply the Active/Disabled split (#52)' (#56) from feat/52-server-sets into main
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (push) Successful in 10s
CI / Tests (py3.12 / windows-latest) (push) Successful in 20s
CI / Tests (py3.13 / ubuntu-latest) (push) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (push) Successful in 9s
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (push) Successful in 10s
CI / Tests (py3.12 / windows-latest) (push) Successful in 20s
CI / Tests (py3.13 / ubuntu-latest) (push) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (push) Successful in 9s
This commit was merged in pull request #56.
This commit is contained in:
+70
@@ -39,6 +39,12 @@ CONFIG_FILENAME = "claude_desktop_config.json"
|
||||
# we can toggle it back on without losing the definition.
|
||||
DISABLED_KEY = "_disabledMcpServers"
|
||||
|
||||
# Named server sets: {set_name: [enabled server names]}. Same pattern as
|
||||
# DISABLED_KEY — a bcc-owned key Claude ignores, stored in the config file so
|
||||
# sets travel with it. Applying a set enables exactly the listed servers and
|
||||
# parks the rest under DISABLED_KEY.
|
||||
SETS_KEY = "_bccServerSets"
|
||||
|
||||
BACKUP_DIRNAME = ".bcc_backups"
|
||||
MAX_BACKUPS = 15
|
||||
|
||||
@@ -452,6 +458,70 @@ def extract_servers(cfg: dict) -> list[ServerEntry]:
|
||||
return out
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Named server sets (issue #52)
|
||||
# --------------------------------------------------------------------------- #
|
||||
def list_server_sets(cfg: dict) -> dict[str, list[str]]:
|
||||
"""
|
||||
Return {set_name: [enabled server names]} from cfg's SETS_KEY.
|
||||
|
||||
Fail-soft: entries whose value isn't a list of strings (hand-edited or
|
||||
corrupted) are skipped rather than raising, so one bad set never hides
|
||||
the rest.
|
||||
"""
|
||||
raw = cfg.get(SETS_KEY)
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
out: dict[str, list[str]] = {}
|
||||
for name, members in raw.items():
|
||||
if isinstance(members, list) and all(isinstance(m, str) for m in members):
|
||||
out[str(name)] = list(members)
|
||||
return out
|
||||
|
||||
|
||||
def save_server_set(cfg: dict, name: str, servers: list[ServerEntry]) -> list[str]:
|
||||
"""
|
||||
Snapshot the current enabled-server names into cfg under SETS_KEY as
|
||||
`name` (overwriting an existing set of that name). Returns the saved
|
||||
member list. The caller decides when cfg reaches disk (normal Save flow).
|
||||
"""
|
||||
members = [s.name for s in servers if s.enabled]
|
||||
sets = cfg.get(SETS_KEY)
|
||||
if not isinstance(sets, dict):
|
||||
sets = {}
|
||||
cfg[SETS_KEY] = sets
|
||||
sets[name] = members
|
||||
return members
|
||||
|
||||
|
||||
def delete_server_set(cfg: dict, name: str) -> bool:
|
||||
"""Remove set `name` from cfg. Drops SETS_KEY entirely when the last set
|
||||
goes, so untouched configs don't grow an empty bcc key. Returns True if
|
||||
something was deleted."""
|
||||
sets = cfg.get(SETS_KEY)
|
||||
if not isinstance(sets, dict) or name not in sets:
|
||||
return False
|
||||
del sets[name]
|
||||
if not sets:
|
||||
cfg.pop(SETS_KEY, None)
|
||||
return True
|
||||
|
||||
|
||||
def apply_server_set(servers: list[ServerEntry], enabled_names: list[str]) -> list[str]:
|
||||
"""
|
||||
Enable exactly the servers named in `enabled_names`; disable every other
|
||||
entry (in place). Returns the set members that no longer exist in
|
||||
`servers` — the caller surfaces those as a warning, and the rest of the
|
||||
set still applies.
|
||||
"""
|
||||
wanted = set(enabled_names)
|
||||
present: set[str] = set()
|
||||
for s in servers:
|
||||
s.enabled = s.name in wanted
|
||||
present.add(s.name)
|
||||
return sorted(wanted - present)
|
||||
|
||||
|
||||
def resolve_name_collision(name: str, existing: set[str]) -> str:
|
||||
"""
|
||||
Return a name guaranteed not to collide with `existing`.
|
||||
|
||||
Reference in New Issue
Block a user