feat: discover Claude Code project .mcp.json configs as profiles (#53)
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 20s
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 9s

Closes #53

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cowork Supervisor
2026-07-12 13:53:38 -04:00
parent 9f535fb77f
commit 520b1b2ffd
2 changed files with 102 additions and 1 deletions
+44 -1
View File
@@ -282,10 +282,43 @@ def msix_warning_text(
)
def discover_project_configs(claude_json_path: str | os.PathLike) -> list[Profile]:
"""
Find project-scope `.mcp.json` configs known to Claude Code.
`~/.claude.json` keeps a `projects` map keyed by absolute project
directory path (that's what the CLI writes as it's used in each repo).
Any project whose directory has a `.mcp.json` file next to it -- a
standalone file with a top-level `mcpServers` object, same shape BCC
already edits -- is surfaced here as its own profile so it can be opened
via 'Add config...' without hunting for the path by hand.
Fails quiet: a missing/unreadable/malformed `claude_json_path`, or a
`projects` value that isn't a dict, just yields an empty list rather than
raising -- this is best-effort discovery, not a required config load.
"""
try:
cfg = load_config(claude_json_path)
except Exception:
return []
projects = cfg.get("projects")
if not isinstance(projects, dict):
return []
out: list[Profile] = []
for key in sorted(k for k in projects if isinstance(k, str)):
mcp_path = Path(key) / ".mcp.json"
if mcp_path.is_file():
out.append(
Profile(label=f"Project: {Path(key).name}", path=mcp_path, config_exists=True)
)
return out
def discover_profiles() -> list[Profile]:
"""
Find every `Claude*` data directory in the platform's app-support base
(Claude Desktop installs), then also check for a Claude Code global config.
(Claude Desktop installs), then also check for a Claude Code global config
and any project-scope `.mcp.json` configs it knows about.
Claude Desktop: scans the platform app-support folder for any `Claude*`
directory (catches `Claude`, `Claude-Work`, etc.).
@@ -298,6 +331,10 @@ def discover_profiles() -> list[Profile]:
`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.
Project scope: ~/.claude.json also tracks a `projects` map, one entry per
directory Claude Code has been run in; any of those with a `.mcp.json`
file are surfaced as their own profiles too (see
`discover_project_configs`).
"""
base = app_support_base()
out: list[Profile] = []
@@ -319,6 +356,12 @@ def discover_profiles() -> list[Profile]:
cc_cfg = home / ".claude.json"
out.append(Profile(label="Claude Code", path=cc_cfg, config_exists=cc_cfg.is_file()))
existing_paths = {str(p.path) for p in out}
for proj in discover_project_configs(cc_cfg):
if str(proj.path) not in existing_paths:
existing_paths.add(str(proj.path))
out.append(proj)
# 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